From 66a2ddfcd0fece618e9cdd87679480870565248b Mon Sep 17 00:00:00 2001 From: Jake Jarrett Date: Mon, 13 Jun 2016 13:56:34 +1000 Subject: [PATCH] mpris support Added some abstracted functionality so we don't define 4 of the same if statements Added proper stop functionality (Set time to 0 & Pause) Moved mpris keypress events into playerCtrl instead of playerService. Fixed grunt shell for non-linux os' This should be much easier to follow whats going on. --- app/index.html | 1 + app/public/js/common/mprisService.js | 86 +++++++++++++++++++++++++++ app/public/js/common/playerService.js | 44 ++++++++++++++ app/public/js/player/playerCtrl.js | 67 +++++++++++++++------ package.json | 4 ++ tasks/nwjs.js | 3 +- tasks/shell.js | 10 +++- 7 files changed, 195 insertions(+), 20 deletions(-) create mode 100644 app/public/js/common/mprisService.js diff --git a/app/index.html b/app/index.html index 9b69fce5..3681b95a 100644 --- a/app/index.html +++ b/app/index.html @@ -223,6 +223,7 @@

+ diff --git a/app/public/js/common/mprisService.js b/app/public/js/common/mprisService.js new file mode 100644 index 00000000..2188f3bd --- /dev/null +++ b/app/public/js/common/mprisService.js @@ -0,0 +1,86 @@ +"use strict"; + +var gui = require("nw.gui"); + +/** + * mpris integration for linux systems using DBUS + */ +app.factory("mprisService", function($rootScope, $log, $timeout, $window, $state) { + // media keys are supported on osx/windows already anyway + var supportedPlatforms = { + "linux": true, + "win32": false, + "darwin": false + }; + + // We check if the platform is supported + if(!supportedPlatforms[process.platform]) return false; + + // Require mpris + var Player = require("mpris-service"); + + // Initialize & configure mpris + var mprisPlayer = Player({ + canRaise: true, + name: "soundnode", + identity: "Soundnode", + desktopEntry: "soundnode", + supportedInterfaces: ["player"], + supportedUriSchemes: ["http", "file"], + supportedMimeTypes: ["audio/mpeg", "application/ogg"] + }); + + // Configuration + mprisPlayer.canControl = true; + mprisPlayer.canSeek = false; + + // When the user asks dbus to show the player, we'll show it. + mprisPlayer.on("raise", function() { + gui.Window.get().show(); + }); + + /** Functions **/ + + /** + * This is called whenever you play/resume a track. + * + * @param trackid {number} (SC Track ID) + * @param length {number} (Microseconds - Integer) + * @param artwork {uri} + * @param title {string} + * @param artist {string} + */ + mprisPlayer.play = function(trackid, length, artwork, title, artist) { + /** Overrides **/ + length = 0; // UNSUPPORTED + + mprisPlayer.metadata = { + "mpris:trackid": mprisPlayer.objectPath("track/" + trackid), + "mpris:length": length, + "mpris:artUrl": artwork, + "xesam:title": title, + "xesam:album": "", + "xesam:artist": artist + }; + + // Tell dbus/mpris that we're currently playing. + mprisPlayer.playbackStatus = "Playing"; + }; + + /** + * Tells mpris that we're paused. + */ + mprisPlayer.pause = function() { + mprisPlayer.playbackStatus = "Paused"; + }; + + /** + * This is called whenever you stop playback + */ + mprisPlayer.stop = function() { + mprisPlayer.playbackStatus = "Stopped"; + }; + + // Return the mprisPlayer object + return mprisPlayer; +}); diff --git a/app/public/js/common/playerService.js b/app/public/js/common/playerService.js index 7c314e85..5fba101c 100644 --- a/app/public/js/common/playerService.js +++ b/app/public/js/common/playerService.js @@ -35,6 +35,7 @@ app.factory('playerService', function( $timeout, $window, $state, + mprisService, notificationFactory, queueService, utilsService @@ -137,6 +138,7 @@ app.factory('playerService', function( player.playNewSong = function() { var trackObj = queueService.getTrack(); var trackObjId = trackObj.songId; + var duration = player.elPlayer.duration * 1000; var songNotification; utilsService.deactivateCurrentSong(); @@ -181,6 +183,12 @@ app.factory('playerService', function( // remove the active class from player favorite icon before play new song // TODO: this should check if the current song is already favorited document.querySelector('.player_favorite').classList.remove('active'); + + // mpris only supports linux + if(process.platform === "linux") { + // tell mpris that we're now playing & send off the attributes for dbus to use. + mprisService.play("0", duration, trackObj.songThumbnail, trackObj.songTitle, trackObj.songUser); + } }; // Updates cache when liking or unliking a song, so future checks will be correct @@ -196,8 +204,17 @@ app.factory('playerService', function( * @method playSong */ player.playSong = function() { + var duration = player.elPlayer.duration * 1000; + this.elPlayer.play(); $rootScope.isSongPlaying = true; + + /** + * linux mpris passthrough for media keys & desktop integration + */ + if(process.platform === "linux") { + mprisService.play("0", duration, player.elThumb.src, player.elTitle.innerHTML, player.elUser.innerHTML); + } }; /** @@ -207,6 +224,24 @@ app.factory('playerService', function( player.pauseSong = function() { this.elPlayer.pause(); $rootScope.isSongPlaying = false; + + /** + * linux mpris passthrough for media keys & desktop integration + */ + if(process.platform === "linux") { + mprisService.pause(); + } + }; + + /** + * Sets the songs time to 0 & then pauses the song, stopping the song. + */ + player.stopSong = function() { + // Set time to 0 + player.setSongTime(0); + + // Set a paused state + player.pauseSong(); }; /** @@ -256,6 +291,15 @@ app.factory('playerService', function( this.playNewSong(); }; + /** + * Set the song time. + * @param {Number} time The time in milliseconds you want to set + */ + player.setSongTime = function(time) { + if(isNaN(time)) throw new Error("You can only set time with a number"); + return document.getElementById('player').currentTime = time; + } + /** * Add event listener "on ended" to player */ diff --git a/app/public/js/player/playerCtrl.js b/app/public/js/player/playerCtrl.js index 0426efa5..6e8790ed 100644 --- a/app/public/js/player/playerCtrl.js +++ b/app/public/js/player/playerCtrl.js @@ -4,6 +4,7 @@ app.controller('PlayerCtrl', function ( $scope, $rootScope, playerService, + mprisService, queueService, hotkeys, $state, @@ -147,20 +148,27 @@ app.controller('PlayerCtrl', function ( } }); + /** + * Used between multiple functions, so we'll leave it here so it reduces + * the amount of times we define it. + */ + var togglePlayPause = function() { + if ( $rootScope.isSongPlaying ) { + playerService.pauseSong(); + } else { + playerService.playSong(); + } + }; + /* * Add native media shortcuts */ - var playPause = new gui.Shortcut({ key: 'MediaPlayPause', active: function() { $scope.$apply(function() { - if ( $rootScope.isSongPlaying ) { - playerService.pauseSong(); - } else { - playerService.playSong(); - } + togglePlayPause(); }); }, failed: function() { @@ -173,7 +181,7 @@ app.controller('PlayerCtrl', function ( active: function() { $scope.$apply(function() { if ( $rootScope.isSongPlaying ) { - playerService.pauseSong(); + playerService.stopSong(); } }); }, @@ -215,6 +223,39 @@ app.controller('PlayerCtrl', function ( gui.App.registerGlobalHotKey(prevTrack); gui.App.registerGlobalHotKey(nextTrack); + /** + * Add native media shortcuts for linux based systems + */ + if(process.platform === "linux") { + // Set a default state + mprisService.playbackStatus = mprisService.playbackStatus || "Stopped"; + + // When the user toggles play/pause + mprisService.on("playpause", function() { + togglePlayPause(); + }); + + // When the user stops the song + mprisService.on("stop", function() { + playerService.stopSong(); + }); + + // When the user requests next song + mprisService.on("next", function() { + playerService.playNextSong(); + }); + + // When the user requests previous song + mprisService.on("previous", function() { + playerService.playPrevSong(); + }); + + // When the user toggles shuffle + mprisService.on("shuffle", function() { + playerService.shuffle(); + }); + } + // function unregister() { // gui.App.unregisterGlobalHotKey(shortcut); // } @@ -228,11 +269,7 @@ app.controller('PlayerCtrl', function ( description: 'Play/Pause song', callback: function(event) { event.preventDefault(); - if ( $rootScope.isSongPlaying ) { - playerService.pauseSong(); - } else { - playerService.playSong(); - } + togglePlayPause(); } }); @@ -240,11 +277,7 @@ app.controller('PlayerCtrl', function ( combo: ['command+return', 'ctrl+return'], description: 'Play/Pause song', callback: function() { - if ( $rootScope.isSongPlaying ) { - playerService.pauseSong(); - } else { - playerService.playSong(); - } + togglePlayPause(); } }); diff --git a/package.json b/package.json index 9f335239..610f8c66 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "time-grunt": "^1.2.2", "webpack": "^1.12.2" }, + "optionalDependencies": { + "dbus": "MarshallOfSound/node-dbus#linux-only", + "mpris-service": "MarshallOfSound/mpris-service" + }, "dependencies": { "react": "^0.14.2", "react-dom": "^0.14.2", diff --git a/tasks/nwjs.js b/tasks/nwjs.js index db7a5917..5b84a9ce 100644 --- a/tasks/nwjs.js +++ b/tasks/nwjs.js @@ -16,7 +16,8 @@ var config = { '<%= settings.app %>/public/img/**/*', '<%= settings.app %>/public/stylesheets/css/**/*', '<%= settings.app %>/dist/**/*', - './node_modules/universal-analytics/**/*' + './node_modules/universal-analytics/**/*', + './node_modules/mpris-service/**/*' ] }; diff --git a/tasks/shell.js b/tasks/shell.js index 46277e91..022897d8 100644 --- a/tasks/shell.js +++ b/tasks/shell.js @@ -3,6 +3,12 @@ module.exports = { stdout: true }, target: { - command: 'webpack -p' + command: function() { + if("linux" === process.platform) { + return 'webpack -p && export PYTHON=/usr/bin/python2 && cd ./node_modules/mpris-service/node_modules/dbus && nw-gyp rebuild --target=0.12.3'; + } else { + return 'webpack -p'; + } + } } -}; \ No newline at end of file +};