用canvas實現視訊播放與彈幕功能

Annnnty發表於2017-12-07

寫在最前

本次分享一下使用canvas來進行視訊播放並且新增彈幕功能。

歡迎關注我的部落格,不定期更新中——

效果圖

示例原始碼見:原始碼地址
ezgif com-optimize

可以看到上方為一段視訊,下面是用canvas來重新繪製的視訊,並且支援動態的新增彈幕。

canvas載入視訊

canvas中的drawImage方法繪製圖片所需要的資料來源不單單是某張圖片,同樣可以是使用視訊的某一幀來進行繪製。就像這樣:

var video = document.getElementById('video')
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var ctx.drawImage(video, 0, 0, width, height);//當視訊開始播放後觸發這個方法可以開始繪製視訊

為什麼通過canvas繪製視訊?

因為canvas提供了getImageData && putImageData方法使得操作者可以動態得來更改每一幀影像的顯示狀態,如果你知道它應該怎麼變:)

比如像MDN中提到的可以對上面這段視訊中的黃色背景進行色調的變化:mdn示例地址

this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
    let frame = this.ctx1.getImageData(0, 0, this.width, this.height);
    let l = frame.data.length / 4;

    for (let i = 0; i < l; i++) {
      let r = frame.data[i * 4 + 0];
      let g = frame.data[i * 4 + 1];
      let b = frame.data[i * 4 + 2];
      if (g > 100 && r > 100 && b < 43)
        frame.data[i * 4 + 3] = 0; //將視訊黃色部分的透明度進行了變化
    }
    this.ctx2.putImageData(frame, 0, 0);

視訊中效果截圖如下:
image
更多關於canvas的影像操作可以參考下面這兩篇文章:
- 基於canvas實現波浪式繪製圖片
- 基於canvas實現的一個截圖小demo

基於canvas的影像處理可以實現很強大的功能,比如濾鏡啊之類的~

騰訊的Aolly Team團隊出品的AlloyImage - 基於HTML5技術的專業影像處理庫就是個很好的範例。作者就搞不明白那些高深的東西了,什麼拉普拉斯運算元,各種運算元:)

彈幕功能

彈幕功能分為兩部分:
- 監聽新彈幕的推送
- 渲染彈幕到頁面

監聽新彈幕的推送

通過維護一個彈幕陣列來實時去渲染每一個彈幕字條的應有位置。而何時更新這個陣列,為了解耦作者使用了釋出訂閱的方式來進行陣列的更新。當然這裡並不是一定要使用這種模式,只不過作者剛剛學習完所以拿來用一下而已。千萬別噴我:)

var Event = (function(){
    var list = {},
        listen,
        trigger,
        remove;
        listen = function(key,fn){ /收集監聽事件
            if(!list[key]) {
                list[key] = [];
            }
            list[key].push(fn);
        };
        trigger = function(){/觸發後依次執行回撥
            var key = Array.prototype.shift.call(arguments),
                 fns = list[key];
            if(!fns || fns.length === 0) {
                return false;
            }
            for(var i = 0, fn; fn = fns[i++];) {
                fn.apply(this,arguments);
            }
        };
        remove = function(key,fn){
            var fns = list[key];
            if(!fns) {
                return false;
            }
            if(!fn) {
                fns && (fns.length = 0);
            }else {
                for(var i = fns.length - 1; i >= 0; i--){
                    var _fn = fns[i];
                    if(_fn === fn) {
                        fns.splice(i,1);
                    }
                }
            }
        };
        return {
            listen: listen,
            trigger: trigger,
            remove: remove
        }
})();
//呼叫方式
Event.listen('data', addNewWord)

$('#submit').click(function() { //點選傳送後便觸發data事件
  var data = $('input').val()
    Event.trigger('data', {
      value: data,
    })
})

function addNewWord (data) {
    var newWord = new Barrage(this.canvas, this.ctx, data) //構建新的彈幕例項
    wordObj.push(newWord)
},

渲染彈幕到頁面

宣告瞭一個彈幕的建構函式,內部包含了其各種屬性並且在原型鏈中新增了draw方法來進行繪製:

function Barrage(canvas, ctx, data) {
    this.width = canvas.width
    this.height = canvas.height
    this.ctx = ctx
    this.color = data.color || '#'+Math.floor(Math.random()*16777215).toString(16) //隨機顏色
    this.value = data.value
    this.x = this.width //x座標
    this.y = Math.random() * this.height
    this.speed = Math.random() + 0.5
    this.fontSize = Math.random() * 10 + 12
}
Barrage.prototype.draw = function() {
        if(this.x < -200) {
            return
        } else {
            this.ctx.font = this.fontSize + 'px "microsoft yahei", sans-serif';
            this.ctx.fillStyle = this.color
            this.x = this.x - this.speed
            this.ctx.fillText(this.value, this.x, this.y)
        }
}

最後

慣例po作者的部落格,不定時更新中——
有問題歡迎在issues下交流。

相關文章