From d7243f5b3bb6b03d16026e6a0e60a7153f3fd020 Mon Sep 17 00:00:00 2001 From: Matthieu Lalonde Date: Fri, 9 Nov 2012 22:03:06 -0500 Subject: [PATCH] It plays stuff, sort off --- resources/js/app.js | 52 ++- resources/js/libs/mimes.js | 81 ++++ resources/js/main.js | 56 ++- resources/js/models/client.js | 32 +- resources/js/models/server.js | 2 + resources/js/modules/webaudio.js | 94 ++++- resources/js/views/client.js | 4 + resources/js/views/footer.js | 2 +- resources/js/views/list.js | 35 ++ resources/js/views/main.js | 14 + resources/js/views/player.js | 391 ++++++++++++++++-- resources/js/views/player.min.js | 18 + resources/templates/app/modal-fatal.html | 5 +- resources/templates/player/player-status.html | 2 +- 14 files changed, 716 insertions(+), 72 deletions(-) create mode 100644 resources/js/libs/mimes.js create mode 100644 resources/js/views/player.min.js diff --git a/resources/js/app.js b/resources/js/app.js index 26d0174..ffc5193 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -81,6 +81,8 @@ _ , initialize: function() { _.bindAll(this + , "_serversInited" + , "_routerActionAbout" , "_routerActionPlaylist" , "_routerActionClient" @@ -105,14 +107,19 @@ _ this.playlist = new PlaylistCollection(); - this.servers.on("add", function (server) { - server.save(); - }); + var serverLength = 0 + , serverCbInit = function (server) { + if (serverLength > 1) { + --serverLength; + } else { + that._serversInited(); + } + }; async.waterfall([ function __asyncAppFetchServerCollection(callback) { that.servers.fetch({ - success: function (collection, modelsAttributes) { + success: function __fnApEventServersFetched(collection, modelsAttributes) { if (modelsAttributes.length === 0) { console.info("Adding default daap server"); collection.add(Config.daap); @@ -126,11 +133,14 @@ _ } , function __asyncAppAttachStateEvents(servers, callback) { - async.forEach(that.servers.models, function (server) { - that._serverInit(server); + serverLength = servers.length; + + async.forEach(that.servers.models, function __fnAppCbServerInit(server) { + that._serverInit(server, serverCbInit); }); - that.servers.on("add", function (server) { + that.servers.on("add", function __fnAppCbServerAdded(server) { + server.save(); that._serverInit(server); }); @@ -149,6 +159,7 @@ _ that.Views.Player = new PlayerView({ el: that.Views.App.Layout.panes.north + , servers: that.servers }); that.Views.Footer = new FooterView({ @@ -166,12 +177,12 @@ _ that.Views.Main.setView("playlist", that.Views.Playlist); - that.View.Main.on("action:item", function __fnAppEventMainViewActionItem (event) { + that.Views.Main.on("action:item", function __fnAppEventMainViewActionItem (event) { }); - that.View.Main.on("action:playorder", function __fnAppEventMainViewActionItem (event) { - + that.Views.Main.on("action:playorder", function __fnAppEventMainViewActionItem (event) { + that.Views.Player.setPlayIndex(event); }); that.Views.Footer.on("toggle:sidebar", function __fnAppEventTogglerSiderBar (event) { @@ -180,11 +191,11 @@ _ }); that.Views.Footer.on("toggle:random", function __fnAppEventTogglerRandom (event) { - // TODO:... + that.Views.Player.setRandomState(event); }); that.Views.Footer.on("toggle:repeat", function __fnAppEventTogglerRepeater (event) { - // TODO:... + that.Views.Player.setRepeatState(event); }); that.Views.Footer.on("show:about", that.__showModalAbout); @@ -228,6 +239,17 @@ _ return this; } + , _serversInited: function () { + var that = this + ; + + console.debug("All Base Servers Inited!"); + + that.Views.Player.init(); + + that.Views.Player.playAtIndex(0);//Math.floor(Math.random() * that.Views.Player._playIndex.length)); + } + , _routerDefaultAction: function () { var that = this ; @@ -317,7 +339,7 @@ _ that.Views.Main.showView(server.get("hostname")); } - , _serverInit: function(server) { + , _serverInit: function(server, cbInit) { var that = this ; @@ -327,6 +349,10 @@ _ server.client.off("all"); + server.client.on("inited", function __fnAppEventClientInited(client) { + cbInit(null, client); + }); + server.client.on("state", function __fnAppEventClientStateChange(client, state) { if (state === ClientModel.defaults._states.loading) { if (that.loadingStates === 0) { diff --git a/resources/js/libs/mimes.js b/resources/js/libs/mimes.js new file mode 100644 index 0000000..95ce2b5 --- /dev/null +++ b/resources/js/libs/mimes.js @@ -0,0 +1,81 @@ +/*jslint laxbreak:true */ +/*jslint laxcomma:true */ +/*jslint loopfunc:true */ +/*jslint strict:true */ +/*jslint browser:true */ +/*jslint devel:true */ +/** + * TODO: If the user agrees, set the username/passs in a cookie +**/ +define([ +], function ( +) { + "use strict"; + + var MIMESClass = function() {}; + + MIMESClass.prototype.get = function (file) { + var fileExtension = ''; + var dotPos = 0; + var audioType = undefined; + + if ((dotPos = file.indexOf('.')) >= 0) { + fileExtension = file.substring(dotPos + 1); + } else { + fileExtension = file; + } + + switch (fileExtension) { + // TODO: Add ogg/flac (video?) + case "mp3": + audioType = "audio/mpeg"; + break; + case "au": + case "snd": + audioType = "audio/basic"; + break; + case "wav": + audioType = "audio/x-wav"; + break; + case "aif": + case "aifc": + audioType = "audio/aiff"; + break; + + case "m4a": + case "mp4": + case "mp4a": + audioType = "audio/mp4"; + break; + + case "ogg": + case "oga": + audioType = "audio/ogg"; + break; + + case "flac": + audioType = "audio/flac"; + break; + + case "axa": + audioType = "audio/annodex"; + break; + + case "vorb": + audioType = "audio/vorbis"; + break; + + case "spx": + audioType = "audio/speex"; + break; + + default: + console.info('Unknown type: ' + fileExtension); + break; + } + + return audioType; + }; + + return new MIMESClass(); +}); \ No newline at end of file diff --git a/resources/js/main.js b/resources/js/main.js index 607b64b..67fd154 100644 --- a/resources/js/main.js +++ b/resources/js/main.js @@ -4,8 +4,8 @@ /*jslint strict:true */ /*jslint browser:true */ /*jslint devel:true */ -require.config({ - shim: { +requirejs.config({ + shim: { "jquery-ui": ["jquery"] , "jquery-event-drag": ["jquery"] , "jquery-layout": ["jquery", "jquery-ui"] @@ -19,11 +19,11 @@ require.config({ , "bootstrap-contextmenu": ["bootstrap"] - , "jquery": { + , "jquery": { exports: "$" } - , "backbone": { + , "backbone": { //These script dependencies should be loaded before loading //backbone.js deps: [ @@ -38,16 +38,16 @@ require.config({ deps: ["backbone"] } - , "underscore": { + , "underscore": { exports: "_" } - , "toolbox": { + , "toolbox": { exports: "Toolbox" , deps: ["underscore"] } - , "toolbox-extras": { + , "toolbox-extras": { deps: ["toolbox"] } @@ -60,7 +60,7 @@ require.config({ } } - , paths: { + , paths: { "jquery": "../vendors/jquery/1.8.2/jquery.min" , "jquery-ui": "../vendors/jquery/ui/1.9.0/jquery-ui-all" , "jquery-event-drag": "../vendors/jquery/event-drag/2.2/jquery.event.drag" @@ -88,23 +88,45 @@ require.config({ , "async": "../vendors/async" } - , baseUrl: "/resources/js" - , urlArgs: "v=0.3.2" -}); + , baseUrl: "/resources/js" + , urlArgs: "v=0.3.2" -require([ - "app" + , catchError: true +}); +requirejs([ + "underscore" , "jquery" + + , "app" + + , "text!../templates/app/modal-fatal.html" ] -, function __fnMainRequireLoader ( App, $ ) { +, function __fnMainRequireLoader ( +_ +, $ + +, App + +, tmplModalFatal +) { "use strict"; // JQuery Backwords compatibility fix - $.curCSS = $.css; + $.curCSS = $.css; $("body").ready(function __fnAppRequireLoader () { - // We export App to window for debuging purposes. - window.App = new App(); + //try { + // We export App to window for debuging purposes. + window.App = new App(); + /*} catch (e) { + console.log("LAWL"); + $("body").html(""); + var modalFatal = $(_.template(tmplModalFatal)({ + ERROR_REPORTING: e.toString() + })); + + modalFatal.modal(); + }*/ }); }); \ No newline at end of file diff --git a/resources/js/models/client.js b/resources/js/models/client.js index d8e90e7..4c0c195 100644 --- a/resources/js/models/client.js +++ b/resources/js/models/client.js @@ -29,6 +29,7 @@ define([ , "buildRequestUid" , "url" , "urlHost" + , "urlMedia" , "xhr" , "fetchXHR" @@ -40,7 +41,7 @@ define([ , "fetchPlaylists" , "fetchPlaylist" , "fetchBrowse" - , "fetchgenres" + , "fetchGenres" , "fetchArtists" , "fetchAlbums" @@ -172,14 +173,18 @@ define([ uri = request; // Add leading slash here! } - if (_.isNumber(this.collections.session.id)) { + if (_.isNumber(that.collections.session.id)) { var prefix = "?"; if (uri.indexOf("?") >= 0) { prefix = "&"; } - uri += prefix + "session-id=" + this.collections.session.id; + uri += prefix + "session-id=" + that.collections.session.id; + } + + if (uri.charAt(0) !== "/" && uri.charAt(0) !== "?") { + uri = "/" + uri; } return this.attributes.protocol + "://" + this.urlHost() + uri; @@ -192,6 +197,14 @@ define([ return this.attributes.hostname + ":" + this.attributes.port; } + , urlMedia: function (itemid, type, dbid) { + if (_.isUndefined(dbid) || !_.isNumber(dbid)) { + dbid = 1; + } + + return this.url("/databases/" + dbid + "/items/" + itemid + "." + type); + } + // TODO: Could be interesting to set a useragent, could possibly affect transcoding on the server side , xhr: function (url, responseType, callback) { var that = this @@ -292,7 +305,18 @@ define([ var that = this ; - that.fetchXHR("/login", success, error); + // TODO: Check for a cookie here... + that.fetchXHR("/login", function __fetchServerInfoCallback(content) { + var Results = new DMAPModel(content, { + contentCodes: that.collections.contentCodes + }); + + that.collections.session.id = Results.get("dmap_sessionid"); + + if (_.isFunction(success)) { + success(that.collections.session); + } + }, error); } , fetchDatabases: function (success, error) { diff --git a/resources/js/models/server.js b/resources/js/models/server.js index 3d1bbff..4f78196 100644 --- a/resources/js/models/server.js +++ b/resources/js/models/server.js @@ -32,6 +32,8 @@ define([ // that.save(); } + that.trigger("inited", that); + //that.client.fetchDatabases(that.client.fetchDatabase); }); diff --git a/resources/js/modules/webaudio.js b/resources/js/modules/webaudio.js index 1371115..cc1d684 100644 --- a/resources/js/modules/webaudio.js +++ b/resources/js/modules/webaudio.js @@ -24,13 +24,23 @@ _ , preload: "auto" , controls: false , loop: false - , src: "" + , src: false } , obj: new Audio() , $obj: null + , _states: { + stopped: 0 + , error: 1 + , loading: 2 + , playing: 3 + , paused: 4 + } + + , _state: 0 + , constructor: function () { var that = this ; @@ -45,6 +55,8 @@ _ , "__audioError" , "__audioPause" , "__audioPlay" + , "__audioSeeking" + , "__audioSeeked" , "__audioTimeUpdate" , "__audioLoadStart" , "__audioCanPlay" @@ -53,18 +65,40 @@ _ that.$obj = $(that.obj); - that.obj.attributes = _.extend({}, that.obj.attributes, that._defaultOpts); + _.each(that._defaultOpts, function (value, key) { + if (value !== false) { + that.obj.setAttribute(key, value); + } + }); + + that._bindAudio(); return that; } /** * PUBLIC **/ - , loadMedia: function (uri) { - var that = this + , loadMedia: function (uri, mime, callback) { + var that = this + , audioSource = document.createElement('source') ; - that.obj.stop(); + audioSource.src = uri; + that.obj.src = uri; + audioSource.type = mime; + that.obj.type = mime; + + that.obj.innerHTML = ""; + + that.obj.appendChild(audioSource); + + console.debug("Loading media: " + uri + "\t type: " + mime); + + that._state = that._states.loading; + + that.obj.load(); + + return callback && callback(); } /** * PRIVATE @@ -81,7 +115,7 @@ _ //that.$obj.on("suspend", that.__audioSuspend); - //that.$obj.on("abort", that.__audioAbort); + that.$obj.on("abort", that.__audioError); that.$obj.on("error", that.__audioError); @@ -104,9 +138,9 @@ _ //that.$obj.on("canplaythrough", that.__audioCanPlayThrough); - //that.$obj.on("seeking", that.__audioSeeking); + that.$obj.on("seeking", that.__audioSeeking); - //that.$obj.on("seeked", that.__audioSeeked); + that.$obj.on("seeked", that.__audioSeeked); that.$obj.on("timeupdate", that.__audioTimeUpdate); @@ -114,7 +148,7 @@ _ //that.$obj.on("ratechange", that.__audioRateChanged); - //that.$obj.on("durationchange", that.__audioDurationChange); + //that.$obj.on("durationchange", that.__audioDurationChange); // TODO: We probably need to implement this! //that.$obj.on("volumechange", that.__audioVolumeChange); } @@ -123,34 +157,73 @@ _ * EVENTS **/ , __audioProgress: function (event) { + var that = this + ; + that.trigger("progress", event); } + , __audioError: function (event) { + var that = this + ; + + that._state = that._states.stopped; + that.trigger.apply(that, ["state", that._state]); } , __audioPause: function (event) { var that = this ; + that._state = that._states.paused; + + that.trigger.apply(that, ["state", that._state]); } , __audioPlay: function (event) { var that = this ; + that._state = that._states.playing; + + that.trigger.apply(that, ["state", that._state]); + } + + , __audioSeeking: function (event) { + var that = this + ; + + that._state = that._states.loading; + + that.trigger.apply(that, ["state", that._state]); + } + + , __audioSeeked: function (event) { + var that = this + ; + + that.obj.play(); + + that._state = that._states.playing; + + that.trigger.apply(that, ["state", that._state]); } , __audioTimeUpdate: function (event) { var that = this ; + that.trigger("timeupdate", event); } , __audioLoadStart: function (event) { var that = this ; + that._state = that._states.loading; + + that.trigger.apply(that, ["state", that._state]); } , __audioCanPlay: function (event) { @@ -163,6 +236,9 @@ _ var that = this ; + that._state = that._states.stopped; + + that.trigger("ended", event); } }, Backbone.Events)); diff --git a/resources/js/views/client.js b/resources/js/views/client.js index e01a753..c2857aa 100644 --- a/resources/js/views/client.js +++ b/resources/js/views/client.js @@ -121,6 +121,10 @@ _ , dbId: dbId } }); + + that.Views.List.on("action:playorder", function __fnEventClientViewPlayOrder (event) { + that.trigger.apply(that, ["action:playorder", event]); + }); } , resize: function () { diff --git a/resources/js/views/footer.js b/resources/js/views/footer.js index 33b4683..36b82d3 100644 --- a/resources/js/views/footer.js +++ b/resources/js/views/footer.js @@ -97,7 +97,7 @@ _ if (!that.__timeoutDebounceRepeatButton) { that.__timeoutDebounceRepeatButton = setTimeout(function () { - that.trigger("toggle:repeat", that._stateRepeat); + that.trigger("toggle:repeat", that._stateRepeat || null); delete that.__timeoutDebounceRepeatButton; }, 500); diff --git a/resources/js/views/list.js b/resources/js/views/list.js index 76a5d23..c496c13 100644 --- a/resources/js/views/list.js +++ b/resources/js/views/list.js @@ -480,6 +480,8 @@ console.log(parentWidth, that.$el.innerWidth(), parentHeight, that.$el, parentEl that.dataTable.fnDraw(); that.resize(); + + that._setPlayOrder(); } , hide: function () { @@ -488,6 +490,39 @@ console.log(parentWidth, that.$el.innerWidth(), parentHeight, that.$el, parentEl this.$el.hide(); } + + , _setPlayOrder: function () { + var that = this + , playIndex = [] + ; + + playIndex = _.map(that.dataTable.fnSettings().aiDisplay, function (id, index) { + var itemData = that.dataTable.fnGetData(id) + , itemIndex = {} + ; + + if (that.options.type === "playlist") { + itemIndex.hostname = itemData.server; + itemIndex.dbId = itemData.database; + itemIndex.playlist = true; + } else { + itemIndex.hostname = that.options.client.hostname; + itemIndex.dbId = that.options.client.dbId; + itemIndex.playlist = null; + } + + itemIndex.dmap_itemname = itemData["dmap_itemname"]; + itemIndex.daap_songartist = itemData["daap_songartist"]; + itemIndex.daap_songtime = itemData["daap_songtime"]; + + itemIndex.type = itemData["daap_songformat"]; + itemIndex.id = itemData["dmap_persistantid"] || itemData["dmap_itemid"] || itemData["dmap_containeritemid"] || itemData["id"]; + + return itemIndex; + }); + + that.trigger("action:playorder", playIndex); + } }); return List; diff --git a/resources/js/views/main.js b/resources/js/views/main.js index 50ce241..a543e7f 100644 --- a/resources/js/views/main.js +++ b/resources/js/views/main.js @@ -35,6 +35,7 @@ _ , "resizeView" //, "toggleView" , "destroyView" + , "_setPlayOrder" ); //that.setView("playlist", new ListView(playlistCollection)); @@ -67,6 +68,8 @@ _ that.$el.append(that.children[name].$el); } + that.children[name].on("action:playorder", that._setPlayOrder); + if (that.children[name].show) { that.children[name].show(); } @@ -102,9 +105,13 @@ _ that.children[name].$el.hide(); + that.children[name].off("action:player"); + setTimeout(function __fnTimeoutMainViewHide() { that.children[name].$el.detach(); + delete that.current; + return callback && callback(); }, 50); } @@ -131,6 +138,13 @@ _ , destroyView: function (name) { } + + , _setPlayOrder: function (event) { + var that = this + ; + + that.trigger.apply(that, ["action:playorder", event]); + } }); return Main; diff --git a/resources/js/views/player.js b/resources/js/views/player.js index 0dee3cb..6711298 100644 --- a/resources/js/views/player.js +++ b/resources/js/views/player.js @@ -11,6 +11,8 @@ define([ , "modules/webaudio" + , "libs/mimes" + , "text!../../templates/player/layout.html" , "text!../../templates/player/player-status.html" ] @@ -21,6 +23,8 @@ _ , WebAudio +, MIMES + , tmplPlayerLayout , tmplPlayerStatus ) { @@ -50,6 +54,7 @@ _ Player = Backbone.View.extend({ elViewport: null , $elViewport: null + , $elVolumeInput: null , playIndex: [] @@ -61,6 +66,12 @@ _ , _stateRepeat: null + , _statesRepeat: { + none: null + , one: false + , all: true + } + , _stateMute: null , _stateVolume: null @@ -76,22 +87,24 @@ _ , paused: 3 } + , debouncers: {} + , events: { //"click #add-friend": "showPrompt", - "click div:first-child > a.buttonPrev": + "click div:first-child > a.buttonPrev:not(.disabled)": "__buttonPrevious" - , "click div:first-child > a.buttonNext": + , "click div:first-child > a.buttonNext:not(.disabled)": "__buttonNext" - , "click div:first-child > a.buttonPlay": + , "click div:first-child > a.buttonPlay:not(.disabled)": "__buttonPlayPause" // Volume Down - , "click div:first-child > .playerVolumeWrapper > a:first-child": + , "click div:first-child > .playerVolumeWrapper > a:first-child:not(.disabled)": "__buttonVolumeDown" // Volume Up - , "click div:first-child > .playerVolumeWrapper > a:last-child": + , "click div:first-child > .playerVolumeWrapper > a:last-child:not(.disabled)": "__buttonVolumeUp" // Volume click - , "click div:first-child > .playerVolumeWrapper > input": + , "click div:first-child > .playerVolumeWrapper > input:not(disabled)": "__buttonVolumeClick" // Seek input , "click div:last-child > .playerProgressWrapper > progress": @@ -103,12 +116,15 @@ _ ; _.bindAll(this -// Public + // Public + , "init" , "setPlayIndex" + , "sortPlayIndex" , "setRandomState" , "setRepeatState" , "playItem" -// Events + , "playAtIndex" + // Events , "__buttonPlayPause" , "__buttonNext" , "__buttonPrevious" @@ -116,21 +132,31 @@ _ , "__buttonVolumeDown" , "__buttonVolumeClick" , "__buttonSeek" -// Private + // Private , "_waCreate" , "_waBindEvents" , "__waStateChanged" - , "_playerEnded" + , "__waEnded" + , "__waTimeUpdate" + , "_playerPlay" , "_playerLoading" , "_playNext" , "_playPrevious" , "_playerPause" + , "_playerUnpause" , "_playerStop" , "_playerStart" + , "_getVolume" , "_setVolume" + , "_seek" + + , "_playBtnSetState" + , "_disableControls" + , "_enableControls" , "_getItemData" , "_getItemMediaUrl" , "_setViewport" + , "_progressSetLoadding" , "_updateViewportProgress" ); @@ -141,7 +167,9 @@ _ that.elViewport = document.createElement("div"); that.$elViewport = $(that.elViewport); - that.$elViewport.html(_.template(tmplPlayerStatus)({})); + that._setViewport(); + + that.$elVolumeInput = that.$el.find(".playerVolumeWrapper > input"); that.$el.append(that.elViewport); @@ -153,18 +181,42 @@ _ /** * PUBLIC **/ - , setPlayIndex: function (index) { + , init: function () { var that = this ; - that._playIndex = index; - that._playCursor = 0; + that._enableControls(); + + that.$elVolumeInput.val(that.webAudio.obj.volume); + } - if (that._stateRandom) { - that.setRandomState(that._stateRandom); + , setPlayIndex: function (index, cursor) { + var that = this + ; + + if (that.webAudio._state === that.webAudio._states.loading) { + that._playerStop(); } - return that._playIndex; + if (that.webAudio._state < that.webAudio._states.loading) { + that._playIndex = index; + that._playCursor = cursor || 0; + + if (that._stateRandom) { + that.setRandomState(that._stateRandom); + } + + return that._playIndex; + } else { + return undefined; + } + } + + , sortPlayIndex: function (index) { + var that = this + ; + + // TODO } , setRandomState: function (random) { @@ -172,10 +224,13 @@ _ ; if (random === true) { + that._playIndexOrdered = that._playIndex; that._playIndex.shuffle(); - } - // TODO: Set cursor back to the item that was playing + // TODO: Set cursor back to the item that was playing + } else { + that._playIndex = that._playIndexOrdered; + } return that._stateRandom = random; } @@ -188,51 +243,123 @@ _ } , playItem: function (item) { + var that = this + ; + + return that._playerPlay(item); + } + , playAtIndex: function (index) { + var that = this + ; + + that._playCursor = index; + that.playItem(that._playIndex[that._playCursor]); } /** * EVENTS **/ , __buttonPlayPause: function (event) { var that = this + , $target = $(event.target) ; + switch (that.webAudio._state) { + case that.webAudio._states.stopped: + that._playNext(); + break; + + case that.webAudio._states.playing: + that._playerPause(); + break; + + case that.webAudio._states.paused: + that._playerUnpause(); + break; + + default: + break; + } } , __buttonNext: function (event) { var that = this ; + that._playNext(); } , __buttonPrevious: function (event) { var that = this ; + that._playPrevious(); } , __buttonVolumeUp: function (event) { var that = this + , volume = that._getVolume() ; + if (volume < 1) { + if ((volume+0.1) > 1) { + volume = 1; + } else { + volume+= 0.1; + } + } + + that._setVolume(volume); } , __buttonVolumeDown: function (event) { var that = this + , volume = that._getVolume() ; + if (volume > 0) { + if ((volume-0.1) < 0) { + volume = 0; + } else{ + volume-= 0.1; + } + } + + that._setVolume(volume); + } , __buttonVolumeClick: function (event) { - var that = this + var that = this + , clientX = event.clientX + , left = event.target.offsetLeft + , clickoffset = clientX - left + , fraction = clickoffset/event.target.offsetWidth ; + that._setVolume(fraction); } , __buttonSeek: function (event) { - var that = this + var that = this ; + if (_.isUndefined(that.debouncers["__buttonSeek"])) { + var clientX = event.clientX + , left = event.target.offsetLeft + , clickoffset = clientX - left + , percent = clickoffset/event.target.offsetWidth + , seekTime = percent * that.webAudio.obj.duration + ; + + that._seek(seekTime); + + that.debouncers["__buttonSeek"] = setTimeout(function __fnPlayerViewButtonSeekDebounce () { + clearTimeout(that.debouncers["__buttonSeek"]); + + delete that.debouncers["__buttonSeek"]; + }, 800); + } } /** @@ -243,24 +370,84 @@ _ var that = this ; - return that.webAudio = new WebAudio(); + that.webAudio = new WebAudio(); + + that._waBindEvents(); + + return that.webAudio; } , _waBindEvents: function () { + var that = this + ; + + that.webAudio.on("state", function __fnPlayerEventWebAudioState (state) { + that.__waStateChanged.apply(that, arguments); + }); + + that.webAudio.on("ended", function __fnPlayerEventWebAudioState (state) { + that.__waEnded.apply(that, arguments); + }); + that.webAudio.on("timeupdate", function __fnPlayerEventWebAudioState () { + that.__waTimeUpdate.apply(that, arguments); + }); } - , __waStateChanged: function () { + , __waStateChanged: function (state) { var that = this ; + that._playBtnSetState(state); + + switch (state) { + case that.webAudio._states.stopped: + console.debug("Player State Stopped"); + //that._setViewport(); + that._enableControls(); + break; + case that.webAudio._states.error: + console.debug("Player State error"); + that._disableControls(); + break; + case that.webAudio._states.loading: + console.debug("Player State loading"); + that._progressSetLoadding(); + that._disableControls(); + break; + case that.webAudio._states.playing: + console.debug("Player State playing"); + that._enableControls(); + break; + case that.webAudio._states.paused: + console.debug("Player State paused"); + that._enableControls(); + break; + } } -// Player Controls - , _playerEnded: function () { + , __waEnded: function () { var that = this ; + that._playNext(); + } + + , __waTimeUpdate: function (event) { + var that = this + ; + + that._updateViewportProgress(event.target.currentTime, event.target.duration); + } + +// Player Controls + , _playerPlay: function (item) { + var that = this + ; + + that.webAudio.loadMedia(that._getItemMediaUrl(item), MIMES.get(item.type), function __fnPlayerViewCbLoaded () { + that._setViewport(item); + }); } , _playerLoading: function () { @@ -273,24 +460,75 @@ _ var that = this ; + switch (that._stateRepeat) { + case that._statesRepeat.all: + case that._statesRepeat.none: + if (that._stateRepeat === that._statesRepeat.all + && that._playIndex.length === (that._playCursor + 1)) { + that._playCursor = -1; + } + + if (that._playIndex.length > (that._playCursor + 1)) { + that._playerPlay(that._playIndex[++that._playCursor]); + } else { + that._playerStop(); + } + break; + case that._statesRepeat.one: + that._playerPlay(that._playIndex[that._playCursor]); + break; + } } , _playPrevious: function () { var that = this ; + switch (that._stateRepeat) { + case that._statesRepeat.all: + case that._statesRepeat.none: + if (that._stateRepeat === that._statesRepeat.all + && that._playCursor === 0) { + that._playCursor = that._playIndex.length; + } + + if (that._playCursor > 0) { + that._playerPlay(that._playIndex[--that._playCursor]); + } else { + that._playerStop(); + } + break; + case that._statesRepeat.one: + that._playerPlay(that._playIndex[that._playCursor]); + break; + } } , _playerPause: function () { var that = this ; + that.webAudio.obj.pause(); + } + + , _playerUnpause: function () { + var that = this + ; + + that.webAudio.obj.play(); } , _playerStop: function () { var that = this ; + that.webAudio.obj.pause(); + that.webAudio.obj.src = ""; + that.webAudio.obj.innetHTML = ""; + + that._playCursor = 0; + + that._setViewport(); } , _playerStart: function () { @@ -299,35 +537,136 @@ _ } + , _getVolume: function () { + var that = this + ; + + return that.webAudio.obj.volume; + } + , _setVolume: function (volume) { var that = this ; + that.$elVolumeInput.val(volume); + return that.webAudio.obj.volume = volume; + } + + , _seek: function (seek) { + var that = this + ; + + if (that.webAudio._state === that.webAudio._states.playing) { + console.debug("Seeking player to " + seek); + + that.webAudio.obj.currentTime = seek; + } } // Viewport methods - , _getItemData: function (item) { + , _playBtnSetState: function (state) { + var that = this + , $btn = that.$el.find("a.buttonPlay"); + + $btn.removeClass("paused"); + $btn.removeClass("playing"); + $btn.removeClass("stopped"); + + switch (state) { + case that.webAudio._states.stopped: + case that.webAudio._states.paused: + $btn.addClass("paused"); + break; + + case that.webAudio._states.loading: + case that.webAudio._states.ready: + case that.webAudio._states.playing: + $btn.addClass("playing"); + break; + + case -1: + $btn.addClass("stopped"); + break; + } + } + + , _disableControls: function () { + var that = this + ; + + that.$el.find(".enabled").addClass("disabled"); + that.$el.find(".enabled").removeClass("enabled"); + that.$el.find("[enabled]").attr("disabled", true); + that.$el.find("[enabled]").removeAttr("enabled"); + //that.$el.find("progress").attr("disabled", true); + } + + , _enableControls: function () { var that = this ; + that.$el.find(".disabled").addClass("enabled"); + that.$el.find(".disabled").removeClass("disabled"); + that.$el.find("[disabled]").attr("enabled", true); + that.$el.find("[disabled]").removeAttr("disabled"); + //that.$el.find("progress").removeAttr("disabled"); } + /*, _getItemData: function (item) { + var that = this + ; +console.log(that.options.servers.get(item.hostname).client.collections); + return that.options.servers.get(item.hostname).client.collections.databases[item.id].get("dmap_listing").get(item.dbId); + } + */ , _getItemMediaUrl: function (item) { var that = this ; + return that.options.servers.get(item.hostname).client.urlMedia(item.id, item.type, item.dbId); } , _setViewport: function (item) { var that = this ; + item = item || {}; + + that.$elViewport.html(_.template(tmplPlayerStatus)(item)); + } + + , _progressSetLoadding: function () { + var that = this + , $progress = that.$el.find(".playerProgressWrapper > progress") + ; + + $progress.removeAttr("value"); + $progress.attr("disabled", true); } , _updateViewportProgress: function (progress, time) { var that = this ; + progress = progress || 0; + time = time || 1; + + if (that.$el.find("> .playerProgressWrapper")) { + var left = (time - progress) + , frac = progress / time + , precent = frac * 100 + , elapsed = Math.floor(progress / 60).toFixed().pad(2, "0") + ":" + (progress % 60).toFixed().pad(2, "0") + , remain = "-" + Math.floor(left / 60).toFixed().pad(2, "0") + ":" + (left % 60).toFixed().pad(2, "0") + , $progress = that.$el.find(".playerProgressWrapper > progress") + ; + + that.$el.find(".playerProgressWrapper > span:first-child").text(elapsed); + that.$el.find(".playerProgressWrapper > span:last-child").text(remain); + + $progress.removeAttr("disabled"); + $progress.val(frac); + } + } }); diff --git a/resources/js/views/player.min.js b/resources/js/views/player.min.js new file mode 100644 index 0000000..37239f5 --- /dev/null +++ b/resources/js/views/player.min.js @@ -0,0 +1,18 @@ +define(["underscore","jquery","backbone","modules/webaudio","libs/mimes","text!../../templates/player/layout.html","text!../../templates/player/player-status.html"],function(e,g,h,d,a,f,b){if(!e.isFunction(Array.prototype.shuffle)){Array.prototype.shuffle=function(){var j=this.length,l=j; +if(j===0){return false;}while(l--){var m=parseInt(Math.random()*j,10);var k=this[l];this[l]=this[m];this[m]=k;}};}var c=h.View.extend({elViewport:null,$elViewport:null,$elVolumeInput:null,playIndex:[],playCursor:0,webAudio:null,_stateRandom:null,_stateRepeat:null,_stateMute:null,_stateVolume:null,_stateAudio:null,_state:null,_states:{stopped:0,loading:1,playing:2,paused:3},events:{"click div:first-child > a.buttonPrev":"__buttonPrevious","click div:first-child > a.buttonNext":"__buttonNext","click div:first-child > a.buttonPlay":"__buttonPlayPause","click div:first-child > .playerVolumeWrapper > a:first-child":"__buttonVolumeDown","click div:first-child > .playerVolumeWrapper > a:last-child":"__buttonVolumeUp","click div:first-child > .playerVolumeWrapper > input":"__buttonVolumeClick","click div:last-child > .playerProgressWrapper > progress":"__buttonSeek"},initialize:function(i){var j=this; +e.bindAll(this,"init","setPlayIndex","sortPlayIndex","setRandomState","setRepeatState","playItem","__buttonPlayPause","__buttonNext","__buttonPrevious","__buttonVolumeUp","__buttonVolumeDown","__buttonVolumeClick","__buttonSeek","_waCreate","_waBindEvents","__waStateChanged","_playerEnded","_playerLoading","_playNext","_playPrevious","_playerPause","_playerStop","_playerStart","_setVolume","_getItemData","_getItemMediaUrl","_setViewport","_updateViewportProgress"); +c.__super__.initialize.apply(j);j.$el.html(f);j.elViewport=document.createElement("div");j.$elViewport=g(j.elViewport);j.$elViewport.html(e.template(b)({})); +j.$elVolumeInput=j.$el.find(".playerVolumeWrapper > input");j.$el.append(j.elViewport);j._waCreate();return j;},init:function(){var i=this;i.$el.find(".disabled").removeClass("disabled"); +i.$el.find("[disabled]").removeAttr("disabled");i.$elVolumeInput.val(i.webAudio.obj.volume);},setPlayIndex:function(i,k){var j=this;j._playIndex=i;j._playCursor=k||0; +if(j._stateRandom){j.setRandomState(j._stateRandom);}return j._playIndex;},sortPlayIndex:function(i){var j=this;},setRandomState:function(j){var i=this; +if(j===true){i._playIndex.shuffle();}return i._stateRandom=j;},setRepeatState:function(j){var i=this;return i._stateRepeat=j;},playItem:function(j){var i=this; +i.webAudio.loadMedia(i._getItemMediaUrl(j),a.get(j.type));},__buttonPlayPause:function(j){var i=this;},__buttonNext:function(j){var i=this;},__buttonPrevious:function(j){var i=this; +},__buttonVolumeUp:function(k){var j=this,i=j._getVolume();if(i<1){if((i+0.1)>1){i=1;}else{i+=0.1;}}j._setVolume(i);},__buttonVolumeDown:function(k){var j=this,i=j._getVolume(); +if(i>0){if((i-0.1)<0){i=0;}else{i-=0.1;}}j._setVolume(i);},__buttonVolumeClick:function(m){var l=this,k=m.clientX,n=m.target.offsetLeft,i=k-n,j=i/m.target.offsetWidth; +l._setVolume(j);},__buttonSeek:function(n){var m=this,l=m._getItem(),k=n.clientX,o=n.target.offsetLeft,i=k-o,j=i/n.target.offsetWidth,p=(j*(l.daap_songtime/1000)); +m._seek(p);},_waCreate:function(){var i=this;i.webAudio=new d();i._waBindEvents();return i.webAudio;},_waBindEvents:function(){},__waStateChanged:function(){var i=this; +},_playerEnded:function(){var i=this;},_playerLoading:function(){var i=this;},_playNext:function(){var i=this;},_playPrevious:function(){var i=this;},_playerPause:function(){var i=this; +},_playerStop:function(){var i=this;},_playerStart:function(){var i=this;},_getVolume:function(){var i=this;return i.webAudio.obj.volume;},_setVolume:function(j){var i=this; +i.$elVolumeInput.val(j);return i.webAudio.obj.volume=j;},_seek:function(i){var j=this;console.debug("Seeking player to "+i);j.webAudio.obj.pause();j.webAudio.obj.currentTime=i; +j.webAudio.obj.play();},_getItemData:function(j){var i=this;},_getItemMediaUrl:function(j){var i=this;return i.options.servers.get(j.hostname).client.urlMedia(j.id,j.type,j.dbId); +},_setViewport:function(j){var i=this;},_updateViewportProgress:function(i,k){var j=this;}});return c;}); \ No newline at end of file diff --git a/resources/templates/app/modal-fatal.html b/resources/templates/app/modal-fatal.html index ddbe4eb..e142699 100644 --- a/resources/templates/app/modal-fatal.html +++ b/resources/templates/app/modal-fatal.html @@ -5,7 +5,10 @@

A fatal error has occured