Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions crates/engine/src/js_dom_bridge_tests/part09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6109,3 +6109,93 @@ fn test_media_source_mse_face_t8n() {
"append-after-endOfStream:readyState 回转 open + sourceopen 重派(先于 update)+ sb1/sb2 完成事件序"
);
}

/// t8s-3 音频轨 MSE 面:isTypeSupported 音频腿真值(mseAppendAudio 门面在位才
/// true——能力诚实)+ 组合串逐 leg(bilibili DASH 探针形态翻转)+ audio
/// SourceBuffer 盖章与 appendBuffer 路由(mock 桥记录调用通道)。
#[test]
fn test_media_source_mse_audio_face_t8s3() {
use zero_script_sandbox::{Sandbox, V8Sandbox};
let config = zero_script_sandbox::SandboxConfig {
persistent_context: true,
..Default::default()
};
let mut sandbox = V8Sandbox::with_config(config).unwrap();
sandbox.execute(generate_js_dom_shim()).unwrap();
let mutations: Arc<Mutex<Vec<DomMutation>>> = Arc::new(Mutex::new(vec![]));
let dom_html: Arc<Mutex<String>> = Arc::new(Mutex::new("<html><body></body></html>".to_string()));
let page_url: Arc<Mutex<String>> = Arc::new(Mutex::new("https://wpt.test/t.html".to_string()));
let canvas_registry: std::sync::Arc<std::sync::Mutex<crate::js_dom_bridge::CanvasRegistry>> =
std::sync::Arc::new(std::sync::Mutex::new(crate::js_dom_bridge::CanvasRegistry::new()));
register_dom_callbacks(&mut sandbox, &mutations, &dom_html, &page_url, &canvas_registry, None);

// ① 能力诚实(双向判别):无 mseAppendAudio(旧宿主形态)→ audio 腿 false、
// 组合串 false(既有 t8n 钉读数不变);补挂 mseAppendAudio 后同一查询翻 true。
sandbox.execute(
r#"
globalThis.__zwVideoBridge = {
mseCreate: function () { return true; },
mseAppend: function () { return true; },
mseEnd: function () { return true; },
mseSetDuration: function () {},
mseDims: function () { return '320x240'; }
};
globalThis.__t = [
String(MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')),
String(MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')),
String(MediaSource.isTypeSupported('audio/mp4; codecs="opus"')),
String(MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,vp9"'))];"#,
)
.unwrap();
assert_eq!(
sandbox.execute("globalThis.__t.join(',')").unwrap().value,
"false,false,false,false",
"无 mseAppendAudio:audio 腿 false + 组合串 false(既有能力面不虚报)"
);
sandbox.execute(
r#"
globalThis.__zwVideoBridge.mseAppendAudio = function (src, b64) {
this.__a = (this.__a || []); this.__a.push('audio:' + atob(b64).length); return true;
};
globalThis.__zwVideoBridge.mseAppend = function (src, b64) {
this.__a = (this.__a || []); this.__a.push('video:' + atob(b64).length); return true;
};
globalThis.__t2 = [
String(MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')),
String(MediaSource.isTypeSupported('audio/mp4; codecs="mp4a.40.2"')),
String(MediaSource.isTypeSupported('audio/mp4; codecs="opus"')),
String(MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,vp9"')),
String(MediaSource.isTypeSupported('audio/mp4'))];"#,
)
.unwrap();
assert_eq!(
sandbox.execute("globalThis.__t2.join(',')").unwrap().value,
"true,true,false,false,true",
"有 mseAppendAudio:bilibili 组合探针翻 true + audio/mp4 aac true + opus/复合 vp9 false + 裸 audio/mp4 true"
);

// ② 双 SourceBuffer 轨章 + appendBuffer 路由:video SB 走 mseAppend、audio SB
// 走 mseAppendAudio(registry 双 feed 的 shim 侧契约面)。
sandbox.execute(
r#"
globalThis.__zwVideoBridge.__a = [];
var ms = new MediaSource();
globalThis.__msA = ms;
var url = URL.createObjectURL(ms);
ms.addEventListener('sourceopen', function () {
var vsb = ms.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
var asb = ms.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
vsb.appendBuffer(new Uint8Array([1, 1, 1, 1]));
asb.appendBuffer(new Uint8Array([2, 2, 2, 2]));
});
var v = document.createElement('video');
v.src = url;"#,
)
.unwrap();
sandbox.execute("void 0").unwrap();
assert_eq!(
sandbox.execute("__zwVideoBridge.__a.join(',')").unwrap().value,
"video:4,audio:4",
"appendBuffer 轨路由:video SB→mseAppend、audio SB→mseAppendAudio(各 4B)"
);
}
45 changes: 38 additions & 7 deletions crates/engine/src/js_dom_shim/part06.js
Original file line number Diff line number Diff line change
Expand Up @@ -8862,6 +8862,9 @@
_zwMseEt.call(this);
this._zwMs = mediaSource || null;
this._zwUpdating = false;
// t8s-3 轨类型章('video'|'audio')——addSourceBuffer 按 mimeType 盖章,
// appendBuffer 据此路由视频/音频独立字节流(DASH 双轨分缓冲)。
this._zwTrackKind = 'video';
};
globalThis.SourceBuffer.prototype = Object.create(_zwMseEt.prototype);
Object.defineProperty(globalThis.SourceBuffer.prototype, 'updating', {
Expand Down Expand Up @@ -8904,7 +8907,15 @@
try {
if (msObj._zwUrl && typeof globalThis.__zwVideoBridge === 'object'
&& typeof globalThis.__zwVideoBridge.mseAppend === 'function') {
ok = globalThis.__zwVideoBridge.mseAppend(msObj._zwUrl, b64) === true;
// t8s-3 轨路由:video SB → 视频流;audio SB → 独立音频流;audio SB
// 遇门面缺席(不可达面——addSourceBuffer 同源守卫已挡)走失败面,
// 绝不把音频字节写入视频流(能力诚实)。
if (self._zwTrackKind === 'audio') {
ok = typeof globalThis.__zwVideoBridge.mseAppendAudio === 'function'
&& globalThis.__zwVideoBridge.mseAppendAudio(msObj._zwUrl, b64) === true;
} else {
ok = globalThis.__zwVideoBridge.mseAppend(msObj._zwUrl, b64) === true;
}
}
} catch (_eMseAp) {}
if (ok) {
Expand Down Expand Up @@ -8948,23 +8959,41 @@
this._zwAttach = null;
};
globalThis.MediaSource.prototype = Object.create(_zwMseEt.prototype);
// spec isTypeSupported 真值表不虚报:唯一真实可解面 = video/mp4 + H.264
//(avc1/avc3);audio/*(音频轨 MSE 未实现)与其他容器恒 false;容器无 codecs
// 查询按 Chromium 口径 true(载荷不可解时 appendBuffer error 事件如实报)。
// decode-h264 feature 关闭(桥缺失)→ 恒 false。
// spec isTypeSupported 真值表不虚报:video/mp4 + H.264(avc1/avc3)恒真值;
// audio/mp4 + AAC 与组合串音频腿以「音频 append 流在位」(mseAppendAudio
// 门面 = 音频解码链可用,t8s-3)为支持面真值——门面缺席恒 false(能力诚实)。
// 其他容器恒 false;容器无 codecs 查询按 Chromium 口径 true(载荷不可解时
// appendBuffer error 事件如实报)。decode-h264 feature 关闭(桥缺失)→ 恒 false。
// https://www.w3.org/TR/media-source/#dom-mediasource-istypesupported
globalThis.MediaSource.isTypeSupported = function (type) {
if (typeof globalThis.__zwVideoBridge !== 'object'
|| typeof globalThis.__zwVideoBridge.mseCreate !== 'function') return false;
var t = String(type == null ? '' : type).trim().toLowerCase();
if (t.indexOf('video/mp4') !== 0) return false;
// t8s-3 音频腿:audio/mp4 + AAC(mp4a.40.x/aac)——音频 append 流在位才真
//(mseAppendAudio 门面 = 解码链可用;组合串逐 leg 全支持才 true——
// bilibili DASH 探针 'video/mp4; codecs="avc1.42E01E,mp4a.40.2"' 形态)。
var isAudio = t.indexOf('audio/mp4') === 0;
if (!isAudio && t.indexOf('video/mp4') !== 0) return false;
// 音频 leg 支持面 = 音频 append 流在位(解码链可用,能力诚实)。
var audioOk = typeof globalThis.__zwVideoBridge.mseAppendAudio === 'function'
&& typeof globalThis.__zwVideoBridge.mseAppend === 'function';
if (isAudio && !audioOk) return false;
var m = /codecs\s*=\s*"([^"]*)"/.exec(t) || /codecs\s*=\s*'([^']*)'/.exec(t)
|| /codecs\s*=\s*([^;]+)/.exec(t);
if (!m) return true;
var parts = m[1].split(',');
for (var i = 0; i < parts.length; i++) {
var c = parts[i].trim();
if (c && !/^avc[13]\./.test(c)) return false;
if (!c) continue;
// AAC 家族:mp4a.40.x(MPEG-4 Audio,AAC-LC 主形态)与 'aac' 别名。
var isAac = audioOk && /^(mp4a\.40|aac)\b/.test(c);
if (isAudio) {
if (!isAac) return false;
} else if (!/^avc[13]\./.test(c) && !isAac) {
// 组合串逐 leg(bilibili DASH 探针 'avc1…,mp4a.40.2' 形态)——视频腿
// avc1/avc3 或音频腿 AAC 全支持才 true。
return false;
}
}
return true;
};
Expand All @@ -8980,6 +9009,8 @@
_zwMseThrow('InvalidStateError', 'MediaSource is not open');
}
var sb = new globalThis.SourceBuffer(this);
// t8s-3 轨章:audio/mp4 → 音频流(registry 独立 feed);其余按视频。
sb._zwTrackKind = t.trim().toLowerCase().indexOf('audio/') === 0 ? 'audio' : 'video';
this.sourceBuffers._zwPush(sb);
return sb;
};
Expand Down
Loading
Loading