From 7900c2d065a1a3e57b6e7b289a8da54fd2f61879 Mon Sep 17 00:00:00 2001
From: Tony Wasserka <918-neobrain@users.noreply.dev.funkwhale.audio>
Date: Wed, 24 Mar 2021 15:46:53 +0100
Subject: [PATCH] Properly handle redundant MediaSession play/pause requests

MediaSession pause requests may happen even when Funkwhale is already in a
paused state. Previously FW would flip between play/pause without
consideration for the current state instead of doing nothing when
the playback state matches the requested one.

Notably, this made Funkwhale resume audio playback when entering sleep mode
on my system.
---
 front/src/components/Queue.vue              |  7 +++---
 front/src/components/audio/Player.vue       | 14 +++++++-----
 front/src/store/player.js                   | 15 ++++++++++++-
 front/tests/unit/specs/store/player.spec.js | 25 +++++++++++++++++----
 4 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/front/src/components/Queue.vue b/front/src/components/Queue.vue
index 5b85b28807..623d944608 100644
--- a/front/src/components/Queue.vue
+++ b/front/src/components/Queue.vue
@@ -96,7 +96,7 @@
                       v-if="!playing"
                       :title="labels.play"
                       :aria-label="labels.play"
-                      @click.prevent.stop="togglePlay"
+                      @click.prevent.stop="resumePlayback"
                       class="control">
                         <i :class="['ui', 'play', {'disabled': !currentTrack}, 'icon']"></i>
                     </span>
@@ -105,7 +105,7 @@
                       v-else
                       :title="labels.pause"
                       :aria-label="labels.pause"
-                      @click.prevent.stop="togglePlay"
+                      @click.prevent.stop="pausePlayback"
                       class="control">
                         <i :class="['ui', 'pause', {'disabled': !currentTrack}, 'icon']"></i>
                     </span>
@@ -308,7 +308,8 @@ export default {
       unmute: "player/unmute",
       clean: "queue/clean",
       toggleMute: "player/toggleMute",
-      togglePlay: "player/togglePlay",
+      resumePlayback: "player/resumePlayback",
+      pausePlayback: "player/pausePlayback",
     }),
     reorder: function(event) {
       this.$store.commit("queue/reorder", {
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index e8bbb8dd81..27a7657e8f 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -74,7 +74,7 @@
             v-if="!playing"
             :title="labels.play"
             :aria-label="labels.play"
-            @click.prevent.stop="togglePlay"
+            @click.prevent.stop="resumePlayback"
             class="circular button control">
               <i :class="['ui', 'big', 'play', {'disabled': !currentTrack}, 'icon']"></i>
           </button>
@@ -82,7 +82,7 @@
             v-else
             :title="labels.pause"
             :aria-label="labels.pause"
-            @click.prevent.stop="togglePlay"
+            @click.prevent.stop="pausePlayback"
             class="circular button control">
               <i :class="['ui', 'big', 'pause', {'disabled': !currentTrack}, 'icon']"></i>
           </button>
@@ -203,7 +203,7 @@
       </div>
     </div>
     <GlobalEvents
-      @keydown.p.prevent.exact="togglePlay"
+      @keydown.p.prevent.exact="togglePlayback"
       @keydown.esc.prevent.exact="$store.commit('ui/queueFocused', null)"
       @keydown.ctrl.shift.left.prevent.exact="previous"
       @keydown.ctrl.shift.right.prevent.exact="next"
@@ -281,8 +281,8 @@ export default {
     }
     // Add controls for notification drawer
     if ('mediaSession' in navigator) {
-      navigator.mediaSession.setActionHandler('play', this.togglePlay);
-      navigator.mediaSession.setActionHandler('pause', this.togglePlay);
+      navigator.mediaSession.setActionHandler('play', this.resumePlayback);
+      navigator.mediaSession.setActionHandler('pause', this.pausePlayback);
       navigator.mediaSession.setActionHandler('seekforward', this.seekForward);
       navigator.mediaSession.setActionHandler('seekbackward', this.seekBackward);
       navigator.mediaSession.setActionHandler('nexttrack', this.next);
@@ -297,7 +297,9 @@ export default {
   },
   methods: {
     ...mapActions({
-      togglePlay: "player/togglePlay",
+      resumePlayback: "player/resumePlayback",
+      pausePlayback: "player/pausePlayback",
+      togglePlayback: "player/togglePlayback",
       mute: "player/mute",
       unmute: "player/unmute",
       clean: "queue/clean",
diff --git a/front/src/store/player.js b/front/src/store/player.js
index f3dbbb8b79..e4d34b02af 100644
--- a/front/src/store/player.js
+++ b/front/src/store/player.js
@@ -99,7 +99,7 @@ export default {
       commit('errored', false)
       commit('resetErrorCount')
     },
-    togglePlay ({commit, state, dispatch}) {
+    togglePlayback ({commit, state, dispatch}) {
       commit('playing', !state.playing)
       if (state.errored && state.errorCount < state.maxConsecutiveErrors) {
         setTimeout(() => {
@@ -109,6 +109,19 @@ export default {
         }, 3000)
       }
     },
+    resumePlayback ({commit, state, dispatch}) {
+      commit('playing', true)
+      if (state.errored && state.errorCount < state.maxConsecutiveErrors) {
+        setTimeout(() => {
+          if (state.playing) {
+            dispatch('queue/next', null, {root: true})
+          }
+        }, 3000)
+      }
+    },
+    pausePlayback ({commit}) {
+      commit('playing', false)
+    },
     toggleMute({commit, state}) {
       if (state.volume > 0) {
         commit('tempVolume', state.volume)
diff --git a/front/tests/unit/specs/store/player.spec.js b/front/tests/unit/specs/store/player.spec.js
index ac995ab1a8..b406428426 100644
--- a/front/tests/unit/specs/store/player.spec.js
+++ b/front/tests/unit/specs/store/player.spec.js
@@ -112,24 +112,41 @@ describe('store/player', () => {
         ]
       })
     })
-    it('toggle play false', () => {
+    it('toggle playback false', () => {
       testAction({
-        action: store.actions.togglePlay,
+        action: store.actions.togglePlayback,
         params: {state: {playing: false}},
         expectedMutations: [
           { type: 'playing', payload: true }
         ]
       })
     })
-    it('toggle play true', () => {
+    it('toggle playback true', () => {
       testAction({
-        action: store.actions.togglePlay,
+        action: store.actions.togglePlayback,
         params: {state: {playing: true}},
         expectedMutations: [
           { type: 'playing', payload: false }
         ]
       })
     })
+    it('resume playback', () => {
+      testAction({
+        action: store.actions.resumePlayback,
+        params: {state: {}},
+        expectedMutations: [
+          { type: 'playing', payload: true }
+        ]
+      })
+    })
+    it('pause playback', () => {
+      testAction({
+        action: store.actions.pausePlayback,
+        expectedMutations: [
+          { type: 'playing', payload: false }
+        ]
+      })
+    })
     it('trackEnded', () => {
       testAction({
         action: store.actions.trackEnded,
-- 
GitLab