/** * @author Wouter van Wijk * * all kinds functions and vars */ var mopidy // values for controls var play = false var random var repeat var consume var single var mute var volumeChanging var volumeSliding = false var positionChanging var initgui = true var popupData = {} var songlength = 0 var artistshtml = '' var artiststext = '' var songname = '' var songdata = {'track': {}, 'tlid': -1} var playlisttracksScroll var playlistslistScroll var STREAMS_PLAYLIST_NAME = '[Radio Streams]' var STREAMS_PLAYLIST_SCHEME = 'm3u' var uriSchemes = {} // array of cached playlists (not only user-playlists, also search, artist, album-playlists) var playlists = {} var currentplaylist var customTracklists = [] var browseStack = [] var browseTracks = [] var ua = navigator.userAgent var isMobileSafari = /Mac/.test(ua) && /Mobile/.test(ua) var isMobileWebkit = /WebKit/.test(ua) && /Mobile/.test(ua) var isMobile = /Mobile/.test(ua) var isWebkit = /WebKit/.test(ua) // constants PROGRAM_NAME = 'MusicBox' ARTIST_TABLE = '#artiststable' ALBUM_TABLE = '#albumstable' PLAYLIST_TABLE = '#playlisttracks' CURRENT_PLAYLIST_TABLE = '#currenttable' SEARCH_ALL_TABLE = '#allresulttable' SEARCH_ALBUM_TABLE = '#albumresulttable' SEARCH_ARTIST_TABLE = '#artistresulttable' SEARCH_TRACK_TABLE = '#trackresulttable' URI_SCHEME = 'mbw' PLAY_NOW = 0 PLAY_NEXT = 1 ADD_THIS_BOTTOM = 2 ADD_ALL_BOTTOM = 3 PLAY_ALL = 4 PLAY_NOW_SEARCH = 5 MAX_TABLEROWS = 50 // the first part of Mopidy extensions which serve radio streams var radioExtensionsList = ['somafm', 'tunein', 'dirble', 'audioaddict'] var uriClassList = [ ['spotify', 'fa-spotify'], ['spotifytunigo', 'fa-spotify'], ['local', 'fa-file-sound-o'], ['m3u', 'fa-file-sound-o'], ['podcast', 'fa-rss-square'], ['dirble', 'fa-microphone'], ['tunein', 'fa-headphones'], ['soundcloud', 'fa-soundcloud'], ['sc', 'fa-soundcloud'], ['gmusic', 'fa-google'], ['internetarchive', 'fa-university'], ['somafm', 'fa-flask'], ['youtube', 'fa-youtube'], ['yt', 'fa-youtube'], ['audioaddict', 'fa-bullhorn'], ['subsonic', 'fa-folder-open'] ] var uriHumanList = [ ['spotify', 'Spotify'], ['spotifytunigo', 'Spotify Browse'], ['local', 'Local Files'], ['m3u', 'Local Playlists'], ['podcast', 'Podcasts'], ['dirble', 'Dirble'], ['tunein', 'TuneIn'], ['soundcloud', 'SoundCloud'], ['gmusic', 'Google Music'], ['internetarchive', 'Internet Archive'], ['somafm', 'Soma FM'], ['youtube', 'YouTube'], ['audioaddict', 'AudioAddict'], ['subsonic', 'Subsonic'] ] function scrollToTop () { var divtop = 0 $('body,html').animate({ scrollTop: divtop }, 250) } function scrollToTracklist () { var divtop = $('#playlisttracksdiv').offset().top - 120 $('body,html').animate({ scrollTop: divtop }, 250) } // A hack to find the name of the first artist of a playlist. this is not yet returned by mopidy // does not work wel with multiple artists of course function getArtist (pl) { for (var i = 0; i < pl.length; i++) { for (var j = 0; j < pl[i].artists.length; j++) { if (pl[i].artists[j].name !== '') { return pl[i].artists[j].name } } } } // A hack to find the first album of a playlist. this is not yet returned by mopidy function getAlbum (pl) { for (var i = 0; i < pl.length; i++) { if (pl[i].album.name !== '') { return pl[i].album.name } } } function artistsToString (artists, max) { var result = '' max = max || 3 for (var i = 0; i < artists.length && i < max; i++) { if (artists[i].name) { if (i > 0) { result += ', ' } result += artists[i].name } } return result } /** ****************************************************** * break up results and put them in album tables *********************************************************/ function albumTracksToTable (pl, target, uri) { var tmp = '' $(target).html(tmp) $(target).attr('data', uri) } function renderSongLi (song, liID, uri, tlid, renderAlbumInfo) { var name, iconClass var tlidString = '' var tlidParameter = '' var onClick = '' // Determine if the song line item will be rendered as part of an album. if (!song.album || !song.album.name) { iconClass = getMediaClass(song.uri) } else { iconClass = 'trackname' } // Play by tlid if available. if (tlid) { tlidString = '" tlid="' + tlid tlidParameter = '\',\'' + tlid onClick = 'return playTrackQueueByTlid(\'' + song.uri + '\',\'' + tlid + '\');' } else { onClick = 'return playTrackByUri(\'' + song.uri + '\',\'' + uri + '\');' } songLi = '
  • ' + '' + '' + '' + '

    ' + song.name + '

    ' if (renderAlbumInfo) { songLi += '

    ' songLi += renderSongLiTrackArtists(song) if (song.album && song.album.name) { songLi += ' - ' songLi += '' + song.album.name + '

    ' } } songLi += '
  • ' return songLi } function renderSongLiTrackArtists (track) { var html = '' if (track.artists) { for (var i = 0; i < track.artists.length; i++) { html += track.artists[i].name html += (i === track.artists.length - 1) ? '' : ' / ' // Stop after 3 if (i > 2) { html += '...' break } } } return html } function isNewAlbumSection (track, previousTrack) { // 'true' if album name is either not defined or has changed from the previous track. return !track.album || !track.album.name || !previousTrack || !previousTrack.album || !previousTrack.album.name || track.album.name !== previousTrack.album.name } function isMultiTrackAlbum (track, nextTrack) { // 'true' if there are more tracks of the same album after this one. return nextTrack.album && nextTrack.album.name && track.album && track.album.name && track.album.name === nextTrack.album.name } function validateTrackName (track, trackNumber) { // Create name if there is none var name = '' if (!track.name || track.name === '') { name = track.uri.split('/') name = decodeURI(name[name.length - 1]) || 'Track ' + String(trackNumber) } else { name = track.name } return name } function resultsToTables (results, target, uri) { if (!results) { return } $(target).html('') $(target).attr('data', uri) var track, previousTrack, nextTrack, tlid var albumTrackSeen = 0 var renderAlbumInfo = true var liID = '' // Keep a list of track URIs for retrieving of covers var coversList = [] var html = '' var tableid, artistname, name, iconClass var targetmin = target.substr(1) var length = 0 || results.length // Break into albums and put in tables for (i = 0; i < length; i++) { previousTrack = track track = results[i] tlid = '' if (i < length - 1) { nextTrack = results[i + 1] } if ('tlid' in results[i]) { // Get track information from TlTrack instance track = results[i].track tlid = results[i].tlid if (i < length - 1) { nextTrack = results[i + 1].track } } track.name = validateTrackName(track, i) // Leave out unplayable items if (track.name.substring(0, 12) === '[unplayable]') { continue } // Streams if (track.length === -1) { html += '
  • ' + track.name + ' [Stream]

  • ' continue } if (isNewAlbumSection(track, previousTrack)) { // Starting to render a new album in the list. tableid = 'art' + i // Render differently if part of an album if (i < length - 1 && isMultiTrackAlbum(track, nextTrack)) { // Large divider with album cover renderAlbumInfo = false html += '
  • ' html += '' + '' + '

    ' + track.album.name + '

    ' html += renderSongLiTrackArtists(track) html += '

  • ' coversList.push([track.uri, i]) } else { renderAlbumInfo = true if (i > 0) { // Small divider html += '
  •  
  • ' } } albumTrackSeen = 0 } popupData[track.uri] = track liID = targetmin + '-' + track.uri html += renderSongLi(track, liID, uri, tlid, renderAlbumInfo) albumTrackSeen += 1 } tableid = '#' + tableid $(target).html(html) // Retrieve album covers for (i = 0; i < coversList.length; i++) { getCover(coversList[i][0], target + '-cover-' + coversList[i][1], 'small') } } // process updated playlist to gui function playlisttotable (pl, target, uri) { var tmp = '' $(target).html('') var targetmin = target.substr(1) var child = '' for (var i = 0; i < pl.length; i++) { if (pl[i]) { popupData[pl[i].uri] = pl[i] child = '
  • ' child += '

    ' + pl[i].name + 'h1>' child += '

    ' child += '' + timeFromSeconds(pl[i].length / 1000) + '' for (var j = 0; j < pl[i].artists.length; j++) { if (pl[i].artists[j]) { child += pl[i].artists[j].name child += (j === pl[i].artists.length - 1) ? '' : ' / ' // stop after 3 if (j > 2) { child += '...' break } } } child += ' / ' + pl[i].album.name + '

    ' child += '

  • ' tmp += child } } $(target).html(tmp) $(target).attr('data', uri) } function getPlaylistTracks (uri) { if (playlists[uri] && playlists[uri].tracks) { return Mopidy.when(playlists[uri].tracks) } else { showLoading(true) return mopidy.playlists.getItems({'uri': uri}).then(function (refs) { return processPlaylistItems({'uri': uri, 'items': refs}) }, console.error) } } function getUris (tracks) { var results = [] for (var i = 0; i < tracks.length; i++) { results.push(tracks[i].uri) } return results } function getTracksFromUri (uri, full_track_data) { var returnTracksOrUris = function (tracks) { return (full_track_data || false) ? tracks : getUris(tracks) } if (customTracklists[uri]) { return returnTracksOrUris(customTracklists[uri]) } else if (playlists[uri] && playlists[uri].tracks) { return returnTracksOrUris(playlists[uri].tracks) } return [] } // convert time to human readable format function timeFromSeconds (length) { var d = Number(length) var h = Math.floor(d / 3600) var m = Math.floor(d % 3600 / 60) var s = Math.floor(d % 3600 % 60) return ((h > 0 ? h + ':' : '') + (m > 0 ? (h > 0 && m < 10 ? '0' : '') + m + ':' : '0:') + (s < 10 ? '0' : '') + s) } /** ***** Toast ***/ function toast (message, delay, textOnly) { textOnl = textOnly || false message = message || 'Loading...' delay = delay || 1000 $.mobile.loading('show', { text: message, textVisible: true, theme: 'a', textonly: textOnl }) if (delay > 0) { setTimeout(function () { $.mobile.loading('hide') }, delay) } } /** **************** * Modal dialogs * ******************/ function showLoading (on) { if (on) { $('body').css('cursor', 'progress') $.mobile.loading('show', { text: 'Loading data from ' + PROGRAM_NAME + '. Please wait...', textVisible: true, theme: 'a' }) } else { $('body').css('cursor', 'default') $.mobile.loading('hide') } } function showOffline (on) { if (on) { $.mobile.loading('show', { text: 'Trying to reach ' + PROGRAM_NAME + '. Please wait...', textVisible: true, theme: 'a' }) } else { $.mobile.loading('hide') } } // from http://dzone.com/snippets/validate-url-regexp function validUri (str) { var regexp = /^(mms|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/ return regexp.test(str) } function validServiceUri (str) { return validUri(str) || isServiceUri(str) } function getScheme (uri) { return uri.split(':')[0].toLowerCase() } function isStreamUri (uri) { var a = validUri(uri) var b = radioExtensionsList.indexOf(getScheme(uri)) >= 0 return a || b } function getMediaClass (uri) { var scheme = getScheme(uri) for (var i = 0; i < uriClassList.length; i++) { if (scheme === uriClassList[i][0]) { return 'fa ' + uriClassList[i][1] } } return '' } function getMediaHuman (uri) { var scheme = getScheme(uri) for (var i = 0; i < uriHumanList.length; i++) { if (scheme === uriHumanList[i][0]) { return uriHumanList[i][1] } } return '' } function isServiceUri (uri) { var scheme = getScheme(uri) var i = 0 for (i = 0; i < uriClassList.length; i++) { if (scheme === uriClassList[i][0]) { return true } } for (i = 0; i < radioExtensionsList.length; i++) { if (scheme === radioExtensionsList[i]) { return true } } return false } function isFavouritesPlaylist (playlist) { return (playlist.name === STREAMS_PLAYLIST_NAME && getScheme(playlist.uri) === STREAMS_PLAYLIST_SCHEME) } function isSpotifyStarredPlaylist (playlist) { var starredRegex = /spotify:user:.*:starred/g return (starredRegex.test(playlist.uri) && playlist.name === 'Starred') }