在H5 worker 系列三 webworkify處理音視頻解碼中提到了 flv.js使用webworkify進行音視頻解碼肖爵,那么在解碼的worker線程中,如果想使用公共的Log模塊鳖敷,想把log日志集成到主線程中丹泉,要怎么做呢元潘?
一、worker線程和主線程關于log的數據
//logger.js
Log.GLOBAL_TAG = 'flv.js';
Log.FORCE_GLOBAL_TAG = false;
Log.ENABLE_ERROR = true;
Log.ENABLE_INFO = true;
Log.ENABLE_WARN = true;
Log.ENABLE_DEBUG = true;
Log.ENABLE_VERBOSE = true;
Log.ENABLE_CALLBACK = false;
Log.emitter = new EventEmitter();
log.js里有很多開關贡珊,當改變這些開關值時,需要使用logging_control.js中的一些get/set方法涉馁,比如:
//logging-control.js
static get enableWarn() {
return Log.ENABLE_WARN;
}
static set enableWarn(enable) {
Log.ENABLE_WARN = enable;
LoggingControl._notifyChange();
}
static _notifyChange() {
let emitter = LoggingControl.emitter;
if (emitter.listenerCount('change') > 0) {
let config = LoggingControl.getConfig();
emitter.emit('change', config);
}
}
static registerListener(listener) {
LoggingControl.emitter.addListener('change', listener);
}
static removeListener(listener) {
LoggingControl.emitter.removeListener('change', listener);
}
這里使用了EventEmitter拋出change事件门岔,來通知一下registerListener中注冊的handler。關于EventEmitter基礎知識谨胞,可以參考Node.js EventEmitter
搜索了下固歪,使用registerListener注冊的,也只有transmuxer.js使用webworker的時候胯努。關于webworker基礎知識牢裳,參考H5 worker 系列三 webworkify處理音視頻解碼
this.e = {
onLoggingConfigChanged: this._onLoggingConfigChanged.bind(this)
};
LoggingControl.registerListener(this.e.onLoggingConfigChanged);
_onLoggingConfigChanged(config) {
if (this._worker) {
this._worker.postMessage({cmd: 'logging_config', param: config});
}
}
然后看看worker里怎么處理的:
case 'logging_config': {
let config = e.data.param;
LoggingControl.applyConfig(config);
if (config.enableCallback === true) {
LoggingControl.addLogListener(logcatListener);
} else {
LoggingControl.removeLogListener(logcatListener);
}
break;
applyConfig是啥
static getConfig() {
return {
globalTag: Log.GLOBAL_TAG,
forceGlobalTag: Log.FORCE_GLOBAL_TAG,
enableVerbose: Log.ENABLE_VERBOSE,
enableDebug: Log.ENABLE_DEBUG,
enableInfo: Log.ENABLE_INFO,
enableWarn: Log.ENABLE_WARN,
enableError: Log.ENABLE_ERROR,
enableCallback: Log.ENABLE_CALLBACK
};
}
static applyConfig(config) {
Log.GLOBAL_TAG = config.globalTag;
Log.FORCE_GLOBAL_TAG = config.forceGlobalTag;
Log.ENABLE_VERBOSE = config.enableVerbose;
Log.ENABLE_DEBUG = config.enableDebug;
Log.ENABLE_INFO = config.enableInfo;
Log.ENABLE_WARN = config.enableWarn;
Log.ENABLE_ERROR = config.enableError;
Log.ENABLE_CALLBACK = config.enableCallback;
}
這里就讓人費解了,在_notifyChange中拋出的getConfig叶沛,繞到worker里蒲讯,又把配置用applyConfig寫回去了,莫非worker這個線程里灰署,和主線程讀取的不是一份數據判帮?必須通過postMessage這種方式同步么局嘁??
二晦墙、測試一下悦昵,確實不是一份數據
1.先改一下transmuxing-worker.js偵聽message方法,無論收到啥消息晌畅,在最后console一下
self.addEventListener('message', function (e) {
switch (e.data.cmd) {
...
}
console.log('test' + Log.FORCE_GLOBAL_TAG + ',event:' + e.data.cmd);
這樣運行一下但指,forceGlobalTag默認值是false,沒問題
2.在transmuxer.js添加一個test方法
test() {
Log.FORCE_GLOBAL_TAG = true;
// LoggingControl.forceGlobalTag = true;
console.log('Log.FORCE_GLOBAL_TAG' + Log.FORCE_GLOBAL_TAG);
this._worker.postMessage({cmd: 'test'});
}
3.在flv-player.js中添加一個test方法
test() {
this._transmuxer.test();
}
4.在index.html中抗楔,改一下暫停按鈕的操作
function flv_pause() {
player.test();
}
5.現(xiàn)在結論出來了棋凳,直接改Log.FORCE_GLOBAL_TAG之后,在worker中讀取這個值连躏,還是原先的值剩岳。而使用LoggingControl.forceGlobalTag觸發(fā)_notifyChange這種方式就可以。把LoggingControl.applyConfig(config);
注釋掉入热,也可以證實這一點拍棕。
三、ENABLE_CALLBACK屬性
1.普通模式
從logger.js中可以看出才顿,ENABLE_CALLBACK默認值為false莫湘,如果為true的時候,在調用logger的幾個輸出方法時郑气,會拋通知出來幅垮,比如
static v(tag, msg) {
if (!tag || Log.FORCE_GLOBAL_TAG)
tag = Log.GLOBAL_TAG;
let str = `[${tag}] > ${msg}`;
if (Log.ENABLE_CALLBACK) {
Log.emitter.emit('log', 'verbose', str);
}
...
這個事件在logging-control.js中有處理
static addLogListener(listener) {
Log.emitter.addListener('log', listener);
if (Log.emitter.listenerCount('log') > 0) {
Log.ENABLE_CALLBACK = true;
LoggingControl._notifyChange();
}
}
static removeLogListener(listener) {
Log.emitter.removeListener('log', listener);
if (Log.emitter.listenerCount('log') === 0) {
Log.ENABLE_CALLBACK = false;
LoggingControl._notifyChange();
}
}
使用addLogListener可以添加一個handler,這樣在Log.v等方法調用時尾组,就會執(zhí)行這個handler了忙芒。可以測試一下:
//transmuxer.js
addOneLogListener() {
LoggingControl.addLogListener(function (type, str) {
console.log('type:' + type + ',str:' + str);
});
}
mainThreadLogV() {
Log.v('mainThreadLogV', 'msg:');
}
先在html頁面上點擊按鈕觸發(fā)addOneLogListener讳侨,然后點擊按鈕觸發(fā)mainThreadLogV呵萨。一切工作正常!
2.worker模式
當我們執(zhí)行addLogListener時跨跨,還會觸發(fā)_notifyChange潮峦,根據上面的分析,會觸發(fā)worker里的邏輯:
case 'logging_config': {
let config = e.data.param;
LoggingControl.applyConfig(config);
if (config.enableCallback === true) {
LoggingControl.addLogListener(logcatListener);
} else {
LoggingControl.removeLogListener(logcatListener);
}
break;
這里在worker里勇婴,又來了一遍addLogListener或removeLogListener忱嘹,難道與applyConfig一樣,worker中不但數據不同步耕渴。剛才添加的logHandler(其實就是console.log('type:' + type + ',str:' + str);
這個function)也不同步么拘悦?做個試驗,先把LoggingControl.addLogListener(logcatListener);
注釋掉橱脸,然后在worker中添加一個message的響應:
//transmuxing-worker.js
case 'wokerLogV':
Log.v('wokerLogV', 'msg:');
break;
然后在主線程中础米,去讓worker執(zhí)行Log.v
//transmuxer.js
wokerLogV() {
this._worker.postMessage({cmd: 'wokerLogV'});
}
再在html頁面上點擊觸發(fā)wokerLogV分苇,果然不出所料,自己添加的logHandler沒有執(zhí)行屁桑。所以logcatListener怎么處理的呢医寿?參考上面的config處理,是用logging_config把數據發(fā)射出去蘑斧,然后在worker線程中用applyConfig這種方式重寫了一遍新數據糟红。但是logHandler就不方便發(fā)射了,只能用worker線程把這兩個參數轉發(fā)回去乌叶。也就是說,不管在main線程上使用addLogListener添加了多少個logHandler柒爸,在worker線程中始終只添加了一個logcatListener准浴,這個線程就是負責轉發(fā)參數的,發(fā)回到main線程中捎稚,才是真實的logHandler去響應乐横。
let logcatListener = onLogcatCallback.bind(this);
function onLogcatCallback(type, str) {
self.postMessage({
msg: 'logcat_callback',
data: {
type: type,
logcat: str
}
});
}
主線程:
case 'logcat_callback':
Log.emitter.emit('log', data.type, data.logcat);
break;
四、總結
可能我一開始就忽略了一些事情今野,在this._worker = work(TransmuxingWorker);
時葡公,生成的transmuxing-worker.js就完全import了另外的副本:
import Log from '../utils/logger.js';
import LoggingControl from '../utils/logging-control.js';
import Polyfill from '../utils/polyfill.js';
import TransmuxingController from './transmuxing-controller.js';
import TransmuxingEvents from './transmuxing-events.js';
/* post message to worker:
data: {
cmd: string
param: any
}
receive message from worker:
data: {
msg: string,
data: any
}
*/
let TransmuxingWorker = function (self) {
...
雖然transmuxer.js中import的是同樣的logger.js,并且里面有l(wèi)ogger.js很多static公共常量
import EventEmitter from 'events';
import Log from '../utils/logger.js';
import LoggingControl from '../utils/logging-control.js';
import TransmuxingController from './transmuxing-controller.js';
import TransmuxingEvents from './transmuxing-events.js';
import TransmuxingWorker from './transmuxing-worker.js';
import MediaInfo from './media-info.js';
class Transmuxer {
...
說得有點啰嗦条霜,但也印證了催什,兩個線程無法共享數據,必須通過postMessage來交互宰睡,并且交互傳遞的只是字符串化的副本蒲凶,也就是傳統(tǒng)上講的深復制。