[adapters] 完成音频系统的多线程支持

This commit is contained in:
SmallMain 2024-10-31 17:16:22 +08:00
parent b88403f005
commit 195434e30e
No known key found for this signature in database
4 changed files with 189 additions and 29 deletions

View File

@ -7,7 +7,9 @@ if (Audio) {
let elem = this._src._nativeAsset; let elem = this._src._nativeAsset;
// Reuse dom audio element // Reuse dom audio element
if (!this._element) { if (!this._element) {
this._element = __globalAdapter.createInnerAudioContext(); this._element = CC_WORKER_AUDIO_SYSTEM
? new WorkerAudio()
: __globalAdapter.createInnerAudioContext();
} }
this._element.src = elem.src; this._element.src = elem.src;
}, },

View File

@ -1,9 +1,95 @@
const { main } = require("./ipc-worker.js");
const { CC_WORKER_AUDIO_SYSTEM_SYNC_INTERVAL } = globalThis;
var audio_worker = { var audio_worker = {
map: {}, map: {},
create(callback, sn) { timer: null,
this.map[sn] = worker.createInnerAudioContext();
create(callback, id) {
this.map[id] = {
audio: worker.createInnerAudioContext({ useWebAudioImplement: true }),
cache: {
duration: 0,
currentTime: 0,
paused: true,
},
callbacks: {},
};
if (!this.timer) {
this.timer = setInterval(this.ensureUpdate.bind(this), CC_WORKER_AUDIO_SYSTEM_SYNC_INTERVAL);
}
}, },
call(callback, id, type, arg) {
const audio = this.map[id].audio;
switch (type) {
case 0:
audio.play();
break;
case 1:
audio.pause();
break;
case 2:
audio.seek(arg);
break;
case 3:
audio.stop();
break;
case 4:
audio.src = arg;
break;
case 5:
audio.loop = arg;
break;
case 6:
audio.volume = arg;
break;
default:
break;
}
},
ensureUpdate() {
// struct: [id, duration, currentTime, paused, ...id2, duration2]
const infos = [];
for (const id in this.map) {
const data = this.map[id];
const audio = data.audio;
const cache = data.cache;
if (
audio.duration !== cache.duration
|| audio.currentTime !== cache.currentTime
|| audio.paused !== cache.paused
) {
cache.duration = audio.duration;
cache.currentTime = audio.currentTime;
cache.paused = audio.paused;
infos.push(id, cache.duration, cache.currentTime, cache.paused);
}
}
if (infos.length > 0) {
main.audioAdapter.onUpdate(infos);
}
},
on(callback, id, type) {
const data = this.map[id];
data.audio["on" + type]((data.callbacks[type] = data => {
main.audioAdapter.onCallback(id, type, data);
}));
},
off(callback, id, type) {
const data = this.map[id];
data.audio["off" + type](data.callbacks[type]);
delete data.callbacks[type];
},
destroy(callback, id) {
this.map[id].destroy();
delete this.map[id];
},
}; };
module.exports = audio_worker; module.exports = audio_worker;

View File

@ -2,74 +2,144 @@ let _id = 0;
class WorkerAudio { class WorkerAudio {
id = ++_id; id = ++_id;
callbacks = {};
get src() { get src() {
return this._src;
} }
set src(str) { set src(str) {
if (this._src !== str) {
this._src = str;
audioWorkerAdapter.call(this.id, 4, str);
}
} }
_src = "";
get loop() { get loop() {
return this._loop;
} }
set loop(v) { set loop(v) {
if (this._loop !== v) {
this._loop = v;
audioWorkerAdapter.call(this.id, 5, v);
}
} }
_loop = false; _loop = false;
get volume() { get volume() {
return this._volume;
} }
set volume(v) { set volume(v) {
if (this._volume !== v) {
this._volume = v;
audioWorkerAdapter.call(this.id, 6, v);
}
} }
_volume = 1; _volume = 1;
// 只读,从 Worker 单向同步值 // 只读,从 Worker 单向同步值,由于是异步的,部分值会先模拟
duration = 0; duration = 0;
currentTime = 0; currentTime = 0;
paused = true; paused = true;
constructor() { constructor() {
audioWorkerAdapter.create(this);
}
get src() {
}
set src(clip) {
} }
play() { play() {
this.paused = false;
audioWorkerAdapter.call(this.id, 0, null);
} }
pause() { pause() {
this.paused = true;
audioWorkerAdapter.call(this.id, 1, null);
} }
seek() { seek(position) {
this.paused = false;
this.currentTime = position;
audioWorkerAdapter.call(this.id, 2, position);
} }
stop() { stop() {
this.paused = true;
audioWorkerAdapter.call(this.id, 3, null);
} }
destroy() { destroy() {
this.paused = true;
audioWorkerAdapter.destroy(this.id);
} }
} }
[
"Canplay",
"Ended",
"Error",
"Pause",
"Play",
"Seeked",
"Seeking",
"Stop",
"TimeUpdate",
"Waiting",
].forEach(name => {
WorkerAudio.prototype["on" + name] = function (callback) {
audioWorkerAdapter.on(this.id, name, callback);
};
WorkerAudio.prototype["off" + name] = function (callback) {
audioWorkerAdapter.off(this.id, name, callback);
};
});
var audioWorkerAdapter = { var audioWorkerAdapter = {
on(id, callback) { audios: {},
create(audio) {
this.audios[audio.id] = audio;
worker.audio.create([audio.id]);
}, },
off(id, callback) {
call(id, type, arg) {
worker.audio.call([id, type, arg]);
},
on(id, type, callback) {
this.audios[id].callbacks[type] = callback;
worker.audio.on([id, type]);
},
off(id, type, callback) {
delete this.audios[id].callbacks[type];
worker.audio.off([id, type]);
},
onCallback(args, cmdId, callback) {
const id = args[0];
const type = args[1];
const data = args[2];
this.audios[id].callbacks[type](data);
},
onUpdate(args, cmdId, callback) {
// struct: [id, duration, currentTime, paused, ...id2, duration2]
const infos = args[0];
for (let i = 0; i < infos.length; i += 4) {
const id = infos[i];
const duration = infos[i + 1];
const currentTime = infos[i + 2];
const paused = infos[i + 3];
const audio = this.audios[id];
audio.duration = duration;
audio.currentTime = currentTime;
audio.paused = paused;
}
},
destroy(id) {
worker.audio.destroy(id);
delete this.audios[id];
}, },
}; };

View File

@ -25,7 +25,9 @@ export default class Audio extends HTMLAudioElement {
this.readyState = HAVE_NOTHING this.readyState = HAVE_NOTHING
const innerAudioContext = wx.createInnerAudioContext() const innerAudioContext = CC_WORKER_AUDIO_SYSTEM
? new WorkerAudio()
: wx.createInnerAudioContext()
_innerAudioContextMap[this._$sn] = innerAudioContext _innerAudioContextMap[this._$sn] = innerAudioContext