]> git.parisson.com Git - teleforma.git/commitdiff
Video sync WIP
authorYoan Le Clanche <yoanl@pilotsystems.net>
Thu, 14 Mar 2024 14:55:25 +0000 (15:55 +0100)
committerYoan Le Clanche <yoanl@pilotsystems.net>
Thu, 14 Mar 2024 14:55:25 +0000 (15:55 +0100)
teleforma/src/js/video/SyncVideoPlayer.ts
teleforma/src/js/video/VideoComponent.vue
teleforma/src/js/video/VideoPlayer.ts
teleforma/src/js/video/constants.ts
teleforma/src/js/video/main.d.ts

index 1e26f5b9ded1ee135f6379bb267b8a2c6ab9960c..ae304a8136f1ceb41f7620c30593beaec7362f19 100644 (file)
@@ -6,11 +6,12 @@ import VideoPlayer from './VideoPlayer';
 const validStates = {
   [PlayerState.LOADING]: [PlayerState.LOADED],
   [PlayerState.LOADED]: [PlayerState.PLAYING],
-  [PlayerState.PLAYING]: [PlayerState.PAUSE, PlayerState.ENDED],
+  [PlayerState.PLAYING]: [PlayerState.PAUSE, PlayerState.ENDED, PlayerState.BUFFERING],
   [PlayerState.PAUSE]: [PlayerState.PLAYING, PlayerState.ENDED],
   [PlayerState.ENDED]: [PlayerState.PLAYING],
 
-  [PlayerState.BUFFERING]: [],
+  [PlayerState.BUFFERING]: [PlayerState.READY, PlayerState.PAUSE, PlayerState.PLAYING],
+  [PlayerState.READY]: [PlayerState.PLAYING],
   [PlayerState.UNSTARTED]: [],
 };
 
@@ -28,8 +29,8 @@ class SyncVideoPlayer {
   public options: MultiVideoPlayerOptions;
   public $container: Element | null;
   public videoPlayers: VideoPlayer[] = [];
+  public state: PlayerState;
   private readyCount: number;
-  private state: PlayerState;
   private currentTime: number;
 
   constructor(options: MultiVideoPlayerOptions) {
@@ -50,6 +51,23 @@ class SyncVideoPlayer {
     this.currentTime = 0;
   }
 
+  public getDebugInfo() {
+    return {
+      state: this.state,
+      currentTime: this.currentTime,
+      videoPlayers: this.videoPlayers.map(vp => {
+        return {
+          id: vp.videoId,
+          state: vp.videoState,
+          currentTime: vp.getCurrentTime(),
+          duration: vp.getDuration,
+          isMain: vp.main,
+          isPlaying: vp.isPlaying,
+        };
+      }),
+    };
+  }
+
   public mount() {
     this.render();
     this.addVideoPlayers(this.options.videoPlayers);
@@ -70,12 +88,15 @@ class SyncVideoPlayer {
   }
 
   public async changeState(state: PlayerState, videoPlayer: VideoPlayer) {
+    // console.log("change global state", state)
     if (state === PlayerState.BUFFERING) {
+      return this.pause()
+      // console.log("change state to BUFFERING")
+      // return this.pause();
       // TODO: find out how to handle buffering
-      return;
+      // return;
     }
     if (state === PlayerState.PLAYING) {
-      console.log("set state : playing")
       return this.play();
     }
     if (state === PlayerState.PAUSE) {
@@ -99,6 +120,19 @@ class SyncVideoPlayer {
         }
       });
     }
+    if (state === PlayerState.READY) {
+      // player has finished buffering and ready to start
+      // check if every player is ready
+      
+      const allReady = this.videoPlayers.every(vp => {
+        return [PlayerState.READY, PlayerState.PLAYING, PlayerState.PAUSE].includes(vp.videoState);
+      });
+      if(allReady){
+        console.log("all ready, start playing")
+        await this.play();
+      }
+
+    }
     if (state === PlayerState.UNSTARTED) {
       if (this.state === PlayerState.ENDED) {
         if (this.options.loop) {
@@ -109,6 +143,7 @@ class SyncVideoPlayer {
   }
 
   private async moveStateTo(state: PlayerState, success?: (newState: PlayerState, oldState: PlayerState) => void, fail?: (newState: PlayerState, oldState: PlayerState) => void) {
+    if(state === this.state) return;
     if (isValidState(this.state, state)) {
       const oldState = this.state;
       this.state = state;
@@ -127,7 +162,6 @@ class SyncVideoPlayer {
   public async onReady() {
     this.readyCount++;
     if (this.readyCount !== this.videoPlayers.length) return;
-    console.log("READY2")
     await this.moveStateTo(PlayerState.LOADED);
   }
 
index d52a910223b1881b016cb59161d32506659ef118..2ebfbaa49a4394da286a23903f5bba148eca7674 100644 (file)
@@ -14,9 +14,14 @@ const syncVideoPlayer = new SyncVideoPlayer({
 
 // build a list of video ids equal to multivideos length
 const ids = ref(Array.from({ length: multivideos.length }, (_, i) => `video-${i}`))
+const debugInfo = ref({})
 
 onMounted(() => {
   syncVideoPlayer.mount()
+
+  setInterval(() => {
+    debugInfo.value = syncVideoPlayer.getDebugInfo()
+  }, 50)
   // const mainVideo = document.getElementById("main-video").querySelector("video")
 
   // mainVideo.addEventListener("loadeddata", () => {
@@ -49,6 +54,7 @@ onMounted(() => {
 //   thumbnailsVideos!.style.height = `${thumbnailsVideoHeight}px`
 // }
 
+
 async function onPlay() {
   await syncVideoPlayer.play()
 }
@@ -83,6 +89,8 @@ async function onChange(e: Event) {
   const value = (e.target as HTMLInputElement).value
   await syncVideoPlayer.timeTo(parseFloat(value))
 }
+
+
 </script>
 
 <template>
@@ -106,6 +114,13 @@ async function onChange(e: Event) {
     </div>
 
     <div class="controls">
+      MAIN STATE = {{ debugInfo.state }}
+      <ul>
+        <li v-for="player in debugInfo.videoPlayers" :key="player.id">
+          {{ player.id }} - {{ player.state }}
+        </li>
+      </ul>
+
       <button @click="onAdd">Add video</button>
       <button @click="onPlay">Play</button>
       <button @click="onPause">Pause</button>
index d226ef702f7af631a12e0b133c7dec290983ff82..ab549351f24c5b2ee2a4147f54d8ac968a429a47 100644 (file)
@@ -13,6 +13,7 @@ class VideoPlayer {
   public controls: boolean;
   public main: boolean;
   public sound: boolean;
+  public canWait: boolean;
   public videoPlayerArea: Element | null;
   // public videoElement: HTMLVideoElement | null;
   public videoState: PlayerState;
@@ -25,6 +26,7 @@ class VideoPlayer {
     this.videoPlayerConfig = videoPlayerConfig;
     this.main = this.videoPlayerConfig.main ?? false;
     this.sound = this.videoPlayerConfig.sound ?? false;
+    this.canWait = true;
     this.controls = this.videoPlayerConfig.controls ?? player.options.controls ?? true;
     this.loop = player.options.loop ?? false;
 
@@ -75,9 +77,44 @@ class VideoPlayer {
     this.videojs.on('timeupdate', this.onTimeUpdate.bind(this))
     this.videojs.on('seeking', this.onSeeking.bind(this))
     this.videojs.on('ended', this.onStateChange.bind(this, PlayerState.ENDED))
-    this.videojs.on('waiting', this.onStateChange.bind(this, PlayerState.BUFFERING))
+    
     this.videojs.on('pause', this.onStateChange.bind(this, PlayerState.PAUSE))
     this.videojs.on('play', this.onStateChange.bind(this, PlayerState.PLAYING))
+    // this.videojs.on('waiting', () => {
+    //   // console.log("WAITING FOR BUFFERING", this.videoState, e)
+    //   // if(this.isPlaying)
+
+    //     this.onStateChange(PlayerState.BUFFERING)
+    // })
+    this.videojs.on('canplay', () => {
+      // console.log("CAN PLAY IS READTY")
+      // debugger;
+      if (![PlayerState.LOADING, PlayerState.LOADED].includes(this.player.state as PlayerState)) {
+        this.onStateChange(PlayerState.READY)
+      }
+      this.canWait = false
+      setTimeout(() => {
+        this.canWait = true
+      
+      }, 1000)
+    })
+    
+    this.videojs.on('waiting', () => {
+      if(!this.canWait)
+        return
+      if(this.videoElement!.readyState === 4)
+        return
+      console.log("BUFFERING...", this.videoElement!.readyState)
+      if (![PlayerState.LOADING, PlayerState.LOADED].includes(this.player.state as PlayerState)) {
+        this.onStateChange(PlayerState.BUFFERING)
+      }
+    })
+    
+    // this.videojs.on('progress', (e:any) => {
+    //   console.log('progress')
+    //   console.log(e)
+    //   this.onStateChange.bind(this, PlayerState.PLAYING)
+    // })
 
 
     // this.videoElement.addEventListener('play', this.onStateChange.bind(this, PlayerState.PLAYING), false);
@@ -96,7 +133,6 @@ class VideoPlayer {
   }
 
   private onReady() {
-    console.log("READY")
     Logger.debug(this.videoId, '::[onReady]');
     this.timeTo(0);
     this.player.onReady();
@@ -118,12 +154,12 @@ class VideoPlayer {
   }
 
   private onStateChange(state: PlayerState) {
-    console.log("on state change", state)
     Logger.debug(this.videoId, '::[onStateChange]', state);
     this.videoState = state;
 
     const statesToPropagate = [
       PlayerState.BUFFERING,
+      PlayerState.READY,
       PlayerState.PLAYING,
       PlayerState.PAUSE,
       PlayerState.ENDED,
@@ -144,13 +180,16 @@ class VideoPlayer {
       }
     }
 
+    if (state === PlayerState.BUFFERING) {
+      this.isPlaying = false;
+    }
+
     if (statesToPropagate.includes(state)) {
       this.player.changeState(state, this);
     }
   }
 
   private render() {
-    console.log("render")
     // if video area is defined, add video to area
     this.videoPlayerArea = document.querySelector(this.videoPlayerConfig.id);
     if (!this.videoPlayerArea) {
@@ -238,19 +277,24 @@ class VideoPlayer {
    */
 
   public async play() {
-    console.log("start playing !")
+    console.trace()
     if (!this.videoElement) return;
     if (this.videoState === PlayerState.PLAYING) return;
-    console.log("play")
+
+
+    var isPlaying = this.videoElement.currentTime > 0 && !this.videoElement.paused && !this.videoElement.ended  && this.videoElement.readyState > this.videoElement.HAVE_CURRENT_DATA;
+    if (isPlaying) {
+      console.log("already playing")
+      return
+    }
     // debugger;
     // const video = document.getElementById(this.videoElement.id)
     // await video!.play()
+    console.log("play")
     await this.videoElement.play().catch(error => console.log(error)); //.catch(noop);
   }
 
   public async pause() {
-    console.log(this.videoElement)
-    console.log(this.videoId)
     if (!this.videoElement) return;
     if (this.videoState === PlayerState.PAUSE) return;
     await this.videoElement.pause();
@@ -264,9 +308,6 @@ class VideoPlayer {
 
   public setControls(controls: boolean) {
     if (!this.videoElement) return;
-    console.log(this.videojs)
-
-    console.log("set controls", controls)
     this.videojs.controls(controls)
     // this.videojs.player.userActive(false)
     // this.videoElement.controls = controls;
@@ -298,7 +339,6 @@ class VideoPlayer {
   }
 
   public getPlayLength() {
-    console.log("duration", this.getDuration(), this._startSeconds)
     return this.getDuration() - this._startSeconds;
   }
 
index 4c4ff07d8eccf4322e074939c1a628726dd2d884..a40d261ed8f8d4355882f00345ca415e276aee99 100644 (file)
@@ -1,9 +1,10 @@
 export enum PlayerState {
-    UNSTARTED = 'UNSARTED',
+    UNSTARTED = 'UNSTARTED',
     ENDED = 'ENDED',
     PLAYING = 'PLAYING',
     PAUSE = 'PAUSE',
     BUFFERING = 'BUFFERING',
+    READY = 'READY', // ready to be played after buffering
     LOADING = 'LOADING',
     LOADED = 'LOADED',
   }
\ No newline at end of file
index b2aed6e74f8218cb0bd95cf4d18c5990ae3c0379..45f703a983fd465eb7567e48a41c8b0bbb1d7ea5 100644 (file)
@@ -2,6 +2,7 @@
 import VideoPlayer from "./video/VideoPlayer";
 
 type MultiVideoPlayer = {
+  state: PlayerState,
   options: MultiVideoPlayerOptions,
   $container: Element | null,
   videoPlayers: VideoPlayer[],