iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 17
2
Modern Web

用 Javascript 當個影像魔術師系列 第 17

Day 17 - Canvas 影片播放

影片播放

今天來介紹如何播放影片,並且套用我們先前做的濾鏡效果,影片可以想像成由每一張照片組成,而每秒照片的多寡就稱為 fps (Frame per second) 也就是說大家常聽到的 24 fps、30 fps,其實就是代表這個影片一秒中有多少張照片 ( 或者比較專業的說法會成為幀數 ),越高的 fps 代表每秒照片數越多,人眼會感覺越來越流暢,對於反應回饋也會更快 ( 所以在需要高度反應的遊戲裡面 fps 千萬不能太低 ),所以我們只要讓每一張照片套用我們的濾鏡效果,就可以製造影片濾鏡。

一般來說,瀏覽器的刷新率會與螢幕設定保持一致,主流螢幕大都是 60 fps,也就是說在整個流程上,每個 fps 所耗費的時間不能大於 1000 / 60 約等於 16.67 毫秒

首先使用一個 video,並且讓他自動播放跟無限循環

<video
  src="../../../assets/video.mp4"
  autoplay
  loop
  ref="video"
  @play="drawCanvas"
  :style="videoStyle"
></video>

<div class="canvas_container">
    <canvas ref="drawCanvas"> </canvas>
</div>

接著在 play 事件發生的時候同步更新至 Canvas,同時也指定寬高

export default {
    name: 'Video',
    components: {
      ImageSlider
    },
    data() {
      return {
        isPlay: false,
        width: 0,
        height: 0,
        fps: 0
      }
    },
    computed: {
      ...mapState({
        sliderValue: state => state.sliderValue
      }),
      videoStyle() {
        return {
          width: `${this.width}px`,
          height: `${this.height}px`
        }
      }
    },
    methods: {
      drawCanvas() {
        const canvas = this.$refs.drawCanvas
        const video = this.$refs.video
        const context = canvas.getContext('2d')
        this.isPlay = true
        canvas.width = this.width
        canvas.height = this.height
        context.drawImage(video, 0, 0, this.width, this.height)
        const pixelData = context.getImageData(0, 0, this.width, this.height)
        const result = applyFilters(pixelData, this.sliderValue)
        context.putImageData(result, 0, 0)
        requestAnimationFrame(this.drawCanvas)
      }
    },
    mounted() {
      let cw = window.innerWidth - 800
      cw = cw < 800 ? 800 : cw
      const ch = cw * (9 / 16)
      this.width = cw
      this.height = ch
    }
  }

其實做的流程跟之前差不多,在一開始的時候先算出這次影片的寬高,接著在 drawCanvas 的時候,將 video 當作 drawImage 的輸入源,接著流程就跟之前一樣了,套上之前的模組,應該就可以順利的產生同步的輸出

還記得嗎,drawImage 輸入源除了 img 外也可以是 video

requestAnimationFrame

上面有使用到 requestAnimationFrame 這個方法,這邊來介紹一下,還記得一開始說過我們希望更新的頻率大約要等於 60 fps 嗎,如果照著最基本的想法應該是使用 setInterval 如下


// 希望每60毫秒重繪一次
setInterval(()=>{
    somefun()
}, 60)

但我們剛剛提到的是主流電腦螢幕下刷新頻率約為 60 fps,對於不同的裝置不一定會有這麼高的刷新率,也就是說如果遇到比較低刷新率的裝置,上面就多了毫無意義的更新。

而第二點是,每一次的更新不一定能在 16.67 秒完成,因為裡面有可能有複雜的計算或各種原因,這個時候更新的頻率還是維持在這個 fps 的話也是沒有意義的。

requestAnimationFrame,這可以幫我們解決掉上面兩個問題,他會自動偵測目前所使用的頻率,並且在每一次更新結束後才執行傳進去的函數,讓瀏覽器就能照目前更新的狀況來做優化,所以一般來說有需要定時更新的東西如動畫應該都可以使用這個方式。

接著我們在最前面加個簡易的 fps 計算

const current = performance.now()
const fps = 1000 / (current - this.lastCallTime)
this.fps = fps
this.lastCallTime = performance.now()

小結

這是遊牧民族在趕羊唷~真的是滿山滿谷的羊。第一次讀取的時候可能會有點卡,因為用比較高的 fps 去錄製,檔案比較大

今天介紹了從影片中套用之前做的效果,其實影片只是會動的照片而已,當然 video 標籤還有很多可以玩的東西,但是因為不需要做一個完整播放器,所以省略了很多東西,有興趣的讀者可以自行鑽研唷!


上一篇
Day 16 - CSS 原生濾鏡
下一篇
Day 18 - Canvas 影片彈幕
系列文
用 Javascript 當個影像魔術師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言