From f06295a5b39f11493f956a43389e631b9a20218f Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Tue, 23 Oct 2018 20:24:36 +0200
Subject: [PATCH] Fix #586: The progress bar in the player now display loading
 state / buffer loading

---
 changes/changelog.d/586.enhancement   |  1 +
 front/src/components/audio/Player.vue | 47 ++++++++++++++++++++++++++-
 front/src/components/audio/Track.vue  | 36 ++++++++++++++++++++
 front/src/store/player.js             |  8 +++++
 4 files changed, 91 insertions(+), 1 deletion(-)
 create mode 100644 changes/changelog.d/586.enhancement

diff --git a/changes/changelog.d/586.enhancement b/changes/changelog.d/586.enhancement
new file mode 100644
index 00000000..bee8c1c9
--- /dev/null
+++ b/changes/changelog.d/586.enhancement
@@ -0,0 +1 @@
+The progress bar in the player now display loading state / buffer loading (#586)
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index 5387d02b..1d622f33 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -51,7 +51,11 @@
             <p class="timer total">{{durationFormatted}}</p>
           </div>
         </div>
-        <div ref="progress" class="ui small orange inverted progress" @click="touchProgress">
+        <div
+          ref="progress"
+          :class="['ui', 'small', 'orange', 'inverted', {'indicating': isLoadingAudio}, 'progress']"
+          @click="touchProgress">
+          <div class="buffer bar" :data-percent="bufferProgress" :style="{ 'width': bufferProgress + '%' }"></div>
           <div class="bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
         </div>
       </div>
@@ -301,9 +305,11 @@ export default {
     ...mapState({
       currentIndex: state => state.queue.currentIndex,
       playing: state => state.player.playing,
+      isLoadingAudio: state => state.player.isLoadingAudio,
       volume: state => state.player.volume,
       looping: state => state.player.looping,
       duration: state => state.player.duration,
+      bufferProgress: state => state.player.bufferProgress,
       queue: state => state.queue
     }),
     ...mapGetters({
@@ -522,4 +528,43 @@ export default {
   margin: 0;
 }
 
+
+@keyframes MOVE-BG {
+	from {
+		transform: translateX(0px);
+	}
+	to {
+		transform: translateX(46px);
+	}
+}
+
+.indicating.progress {
+  overflow: hidden;
+}
+
+.ui.progress .bar {
+  transition: none;
+}
+
+.ui.inverted.progress .buffer.bar {
+  position: absolute;
+  background-color:rgba(255, 255, 255, 0.15);
+}
+.indicating.progress .bar {
+  left: -46px;
+  width: 200% !important;
+  color: grey;
+  background: repeating-linear-gradient(
+    -55deg,
+    grey 1px,
+    grey 10px,
+    transparent 10px,
+    transparent 20px,
+	) !important;
+
+  animation-name: MOVE-BG;
+	animation-duration: 2s;
+	animation-timing-function: linear;
+	animation-iteration-count: infinite;
+}
 </style>
diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue
index 5626dd4d..32eb6272 100644
--- a/front/src/components/audio/Track.vue
+++ b/front/src/components/audio/Track.vue
@@ -44,11 +44,17 @@ export default {
         }
       },
       onload: function () {
+        self.$store.commit('player/isLoadingAudio', false)
         self.$store.commit('player/resetErrorCount')
         self.$store.commit('player/duration', self.sound.duration())
+        let node = self.sound._sounds[0]._node;
+        node.addEventListener('progress', () => {
+          self.updateBuffer(node)
+        })
       }
     })
     if (this.autoplay) {
+      self.$store.commit('player/isLoadingAudio', true)
       this.sound.play()
       this.$store.commit('player/playing', true)
       this.observeProgress(true)
@@ -91,10 +97,40 @@ export default {
     }
   },
   methods: {
+    updateBuffer (node) {
+      // from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163
+      let range = 0;
+      let bf = node.buffered;
+      let time = node.currentTime;
+      try {
+        while(!(bf.start(range) <= time && time <= bf.end(range))) {
+          range += 1;
+        }
+      } catch (IndexSizeError) {
+        return
+      }
+      let loadPercentage
+      let start =  bf.start(range)
+      let end =  bf.end(range)
+      if (range === 0) {
+        // easy case, no user-seek
+        let loadStartPercentage = start / node.duration;
+        let loadEndPercentage = end / node.duration;
+        loadPercentage = loadEndPercentage - loadStartPercentage;
+      } else {
+        let loaded = end - start
+        let remainingToLoad = node.duration - start
+        // user seeked a specific position in the audio, our progress must be
+        // computed based on the remaining portion of the track
+        loadPercentage = loaded / remainingToLoad;
+      }
+      this.$store.commit('player/bufferProgress', loadPercentage * 100)
+    },
     updateProgress: function () {
       this.isUpdatingTime = true
       if (this.sound && this.sound.state() === 'loaded') {
         this.$store.dispatch('player/updateProgress', this.sound.seek())
+        this.updateBuffer(this.sound._sounds[0]._node)
       }
     },
     observeProgress: function (enable) {
diff --git a/front/src/store/player.js b/front/src/store/player.js
index dc01f368..08492541 100644
--- a/front/src/store/player.js
+++ b/front/src/store/player.js
@@ -8,11 +8,13 @@ export default {
     maxConsecutiveErrors: 5,
     errorCount: 0,
     playing: false,
+    isLoadingAudio: false,
     volume: 0.5,
     tempVolume: 0.5,
     duration: 0,
     currentTime: 0,
     errored: false,
+    bufferProgress: 0,
     looping: 0 // 0 -> no, 1 -> on  track, 2 -> on queue
   },
   mutations: {
@@ -59,12 +61,18 @@ export default {
     playing (state, value) {
       state.playing = value
     },
+    bufferProgress (state, value) {
+      state.bufferProgress = value
+    },
     toggleLooping (state) {
       if (state.looping > 1) {
         state.looping = 0
       } else {
         state.looping += 1
       }
+    },
+    isLoadingAudio (state, value) {
+      state.isLoadingAudio = value
     }
   },
   getters: {
-- 
GitLab