Make writing a habit together! This is the 10th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details

The last article was about Angular projects implementing permission controls. Recently, I saw others using VUE for custom video manipulation on the Internet. Plus the recent implementation of angular custom video requirements, to record, as a communication thought 🤔

The functions are as follows:

  • Play/stop
  • Fast back/fast forward/double speed
  • Sound on/Sound off
  • Go to full screen or exit full screen
  • Enter picture-in-Picture/Exit Picture-in-Picture [Not supported on Android tablets, not recommended]
  • Elapsed time/Total elapsed time
  • Play progress bar function: support to click and drag progress
  • Sound progress bar function: support click, drag progress

As shown in figure:

Let’s implement one by one:

The emphasis here is not on layout, let’s simply define:

<! -- app.component.html -->

<div class="video-page">
  <div class="video-tools">
    <button nz-button nzType="primary" (click) ="play('btn')" style="margin-right: 12px;">Play ✅</button>
    <button nz-button nzType="primary" (click) ="pause('btn')">Suspend ✅</button>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu] ="menuForward" nzPlacement="bottomCenter" style="margin: 0 12px;">Fast forward ✅</button>
        <nz-dropdown-menu #menuForward="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click) ="forwardSecond(10)">Fast forward 10 s</li>
            <li nz-menu-item (click) ="forwardSecond(20)">Fast forward 20 s</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu] ="menuBack" nzPlacement="bottomCenter">Fast rewind ✅</button>
        <nz-dropdown-menu #menuBack="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click) ="retreatSecond(10)">Fast rewind 10 s</li>
            <li nz-menu-item (click) ="retreatSecond(20)">The 20 s to retreat quickly</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <ng-container>
      <button nz-button nz-dropdown [nzDropdownMenu] ="speedUp" nzPlacement="bottomCenter" style="margin: 0 12px;">2 x ✅</button>
        <nz-dropdown-menu #speedUp="nzDropdownMenu">
          <ul nz-menu>
            <li nz-menu-item (click) ="speedUpVideo(1)">normal</li>
            <li nz-menu-item (click) ="speedUpVideo(2)">2 times</li>
            <li nz-menu-item (click) ="speedUpVideo(4)">4 times</li>
          </ul>
        </nz-dropdown-menu>
    </ng-container>
    <button nz-button nzType="primary" (click) ="openOrCloseVoice()">Sound on/Sound off ✅</button>
    <button nz-button nzType="primary" style="margin: 0 12px;" (click) ="toFullScreen()">Full screen ✅</button>
    <br />
    <button nz-button nzType="primary" style="margin-top: 12px;" (click) ="entryInPicture()">Enter picture in Picture ⚠️ Android tablets are not supported</button>
    <button nz-button nzType="primary" style="margin: 12px 12px 0 12px;" (click) ="exitInPicture()">Exit picture-in-picture ⚠️ Android tablets are not supported</button>
    <br />
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">Elapsed time/totalTime: ✅ {{currentTime}} / {{totalTime}}</div>
    <! -- Progress bar -->
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">Progress bar: ✅<div
        class="custom-video_control-bg"
        (mousedown) ="handleProgressDown($event)"
        (mousemove) ="handleProgressMove($event)"
        (mouseup) ="handleProgressUp($event)"
      >
        <div
          class="custom-video_control-bg-outside"
          id="custom-video_control-bg-outside"
        >
          <span
            class="custom-video_control-bg-inside"
            id="custom-video_control-bg-inside"
          ></span>
          <span
            class="custom-video_control-bg-inside-point"
            id="custom-video_control-bg-inside-point"
          ></span>
        </div>
      </div>
    </div>
    <div style="display: flex; justify-content: flex-start; align-items: center; margin: 12px 0;">Sound bar: ✅<div class="custom-video_control-voice">
        <span class="custom-video_control-voice-play">
          <i nz-icon nzType="sound" nzTheme="outline"></i>
        </span>
        <div
          class="custom-video_control-voice-bg"
          id="custom-video_control-voice-bg"
          (mousedown) ="handleVolProgressDown($event)"
          (mousemove) ="handleVolProgressMove($event)"
          (mouseup) ="handleVolProgressUp($event)"
        >
          <div 
            class="custom-video_control-voice-bg-outside"
            id="custom-video_control-voice-bg-outside"
          >
            <span 
              class="custom-video_control-voice-bg-inside"
              id="custom-video_control-voice-bg-inside"
            ></span>
            <span 
              class="custom-video_control-voice-bg-point"
              id="custom-video_control-voice-bg-point"
            ></span>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="video-content">
    <video id="video" class="video" style="width: 100%" poster="assets/poster.png">
      <source type="video/mp4" src="assets/demo.mp4">
      Sorry, your browser doesn't support.
    </video>
  </div>
</div>
Copy the code

Angular Ant Design is used here. Readers who are not familiar with this article can head to Angular for quick development with Ng-Zorro

Play/stop

Call the play() and pause() methods of the video object directly:

// app.component.ts

// Play button events
play(flag: string | undefined) {
  if(flag) this.videoState.playState = true
  this.videoState.play = true
  this.video.play()
}
// Pause button event
pause(flag: string | undefined) :void {
  if(flag) this.videoState.playState = false
  this.video.pause()
  this.videoState.play = false
}
Copy the code

Add a flag to the custom play and pause methods to help control the progress bar below. The code above can be more concise.

Fast back/fast forward/double speed

Here fast rewind, fast forward and double speed are set with different options, which are passed with parameters:

// app.component.ts

// Fast forward the specified time
forwardSecond(second: number) :void {
  this.video.currentTime += second; // Locate the current playtime currentTime
}

// Back up the specified time
retreatSecond(second: number) :void {
  this.video.currentTime -= second
}

/ / times the speed
speedUpVideo(multiple: number) :void {
  this.video.playbackRate = multiple; // Set the current playbackRate
}
Copy the code

Sound on/Sound off

Switch to a video conservation attribute:

// app.component.ts

// Turn the sound on or off
openOrCloseVoice(): void {
  this.video.muted = !this.video.muted;
}
Copy the code

Go to full screen or exit full screen

Full screen operation is also easy, using webkitRequestFullScreen

// app.component.ts

// Full screen operation
toFullScreen(): void {
  this.video.webkitRequestFullScreen()
}
Copy the code

After full screen, press Esc to exit full screen

Enter picture in picture/exit picture in picture

Picture-in-picture is equivalent to popover zoom out video ~

// app.component.ts

// Enter the picture
entryInPicture(): void {
  this.video.requestPictureInPicture()
  this.video.style.display = "none"
}

// Exit picture-in-picture
exitInPicture(): void {
  if(this.document.pictureInPictureElement) {
    this.document.exitPictureInPicture()
    this.video.style.display = "block"}}Copy the code

The style of the video is designed to be unobtrusive…

Elapsed time/Total elapsed time

Record the total duration of the video and the current playing duration of the video. We’ve got the meta information of the video when we’ve come to the component, get the total duration; Update the current duration during video playback.

// app.component.ts

// Initializes the events associated with video
initVideoData(): void {
  // Get the total length of the video
  this.video.addEventListener('loadedmetadata'.() = > {
    this.totalTime = this.formatTime(this.video.duration)
  })
  // The listening time has changed
  this.video.addEventListener('timeupdate'.() = > {
    this.currentTime = this.formatTime(this.video.currentTime) // The current playing time})}Copy the code

FormatTime is a formatting function

Play progress bar function

Monitor the clicking, moving and releasing events of the mouse, divide the playing time of the video and the total events, and calculate the percentage.

// app.component.ts

// Progress bar mouse down
handleProgressDown(event: any) :void {
  this.videoState.downState = true
  this.pause(undefined);
  this.videoState.distance = event.clientX + document.documentElement.scrollLeft - this.videoState.leftInit;
}
// Progress bar scroll bar moves
handleProgressMove(event: any) :void {
  if(!this.videoState.downState) return
  let distanceX = (event.clientX + document.documentElement.scrollLeft) - this.videoState.leftInit
  if(distanceX > this.processWidth) { // error tolerance
    distanceX = this.processWidth;
  }
  if(distanceX < 0) { // error tolerance
    distanceX = 0
  }
  this.videoState.distance = distanceX
  this.video.currentTime = this.videoState.distance / this.processWidth * this.video.duration
}
// Progress bar mouse is up
handleProgressUp(event: any) :void {
  this.videoState.downState = false
  // Video playback
  this.video.currentTime = this.videoState.distance / this.processWidth * this.video.duration
  this.currentTime = this.formatTime(this.video.currentTime)
  if(this.videoState.playState) {
    this.play(undefined)}}Copy the code

Here, we need to calculate the position of the progress bar to obtain the percentage of clicking the progress bar, and then update the current playing time of the video. Of course, we also have error tolerance, such as when the progress bar is negative, the current playback time is 0.

Sound progress bar

We have achieved the operation of the playback progress bar, the implementation of the sound progress bar is very easy to use. The audio progress bar also monitors mouse clicks, moves, and releases. However, this time we are dealing with the known height of the sound div.

// app.component.ts

// Sound bar mouse down
handleVolProgressDown(event: any) {
  this.voiceState.topInit = this.getOffset(this.voiceProOut, undefined).top
  this.volProcessHeight = this.voiceProOut.clientHeight
  this.voiceState.downState = true // Press the mouse button
  this.voiceState.distance = this.volProcessHeight - (event.clientY + document.documentElement.scrollTop - this.voiceState.topInit) 
}
// Sound scroll bar moves
handleVolProgressMove(event: any) {
  if(!this.voiceState.downState) return
    let disY = this.voiceState.topInit + this.volProcessHeight - (event.clientY + document.documentElement.scrollTop)
    if(disY > this.volProcessHeight - 2) { // error tolerance
      disY = this.volProcessHeight - 2
    }
    if(disY < 0) { // error tolerance
      disY = 0
    }
    this.voiceState.distance = disY
    this.video.volume = this.voiceState.distance / this.volProcessHeight
    this.videoOption.volume = Math.round(this.video.volume * 100)}// Sound mouse up
handleVolProgressUp(event: any) {
  this.voiceState.downState = false // Press the mouse button
  let voiceRate =  this.voiceState.distance / this.volProcessHeight
  if(voiceRate > 1) {
    voiceRate = 1
  }
  if(voiceRate < 0) {
    voiceRate = 0
  }
  this.video.volume = voiceRate
  this.videoOption.volume = Math.round(this.video.volume * 100); // Assign a value to the video sound
}
Copy the code

As shown in figure:

Results demonstrate

With that done, let’s show the effect in a GIF:

Full screen, sound and picture-in-picture are difficult to capture, not Gif

For detailed code, please go to video-ng.

【 the 】 ✅