Fixes for cross origin content policy

This commit is contained in:
David Halls
2023-11-05 15:39:21 +00:00
parent fa19f367b3
commit 6435207d93
9 changed files with 105 additions and 31 deletions

View File

@@ -0,0 +1 @@
../ffmpeg.js/ffmpeg-worker-dash.worker.js

View File

@@ -0,0 +1 @@
../ffmpeg.js/ffmpeg-worker-hls.worker.js

View File

@@ -26,6 +26,7 @@ export class MuxReceiver extends EventTarget {
type: 'run', type: 'run',
arguments: [ arguments: [
'-seekable', '0', '-seekable', '0',
//'-loglevel', 'debug',
...ffmpeg_args, ...ffmpeg_args,
...(protocol === 'dash' ? [ ...(protocol === 'dash' ? [
'-f', 'dash', // use dash encoder '-f', 'dash', // use dash encoder
@@ -34,7 +35,7 @@ export class MuxReceiver extends EventTarget {
'-streaming', '1', // fragment data '-streaming', '1', // fragment data
'-dash_segment_type', 'webm', // container type '-dash_segment_type', 'webm', // container type
...protocol_args, ...protocol_args,
'/outbound/output.mpd' `/outbound/output${Math.random()}.mpd`
] : [ ] : [
'-f', 'hls', // use hls encoder '-f', 'hls', // use hls encoder
'-hls_time', '2', // 2 second HLS chunks '-hls_time', '2', // 2 second HLS chunks
@@ -42,8 +43,8 @@ export class MuxReceiver extends EventTarget {
'-hls_list_size', '2', // two chunks in the list at a time '-hls_list_size', '2', // two chunks in the list at a time
'-hls_flags', 'split_by_time', // if you don't have < 2s keyframes '-hls_flags', 'split_by_time', // if you don't have < 2s keyframes
...protocol_args, ...protocol_args,
'/outbound/output.m3u8' // path to media playlist file in virtual FS, `/outbound/output${Math.random()}.m3u8` // path to media playlist file in virtual FS,
// must be under /outbound // must be under /outbound
]) ])
], ],
MEMFS: [ MEMFS: [
@@ -82,16 +83,8 @@ export class MuxReceiver extends EventTarget {
}})); }}));
break; break;
case 'upload': { case 'upload': {
const reader = msg.stream.getReader(); this.dispatchEvent(new CustomEvent('message', { detail: msg }));
const chunks = []; break;
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
chunks.push(value);
}
console.log('upload', msg.url, new Blob(chunks));
} }
} }
}; };

32
site/poster.html Normal file
View File

@@ -0,0 +1,32 @@
<html>
<head>
<script>
window.addEventListener('message', async e => {
const msg = e.data;
const reader = msg.options.stream.getReader();
const chunks = [];
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
chunks.push(value);
}
delete msg.options.stream;
msg.options.body = new Blob(chunks);
fetch(msg.url, msg.options).then(response => {
//check_exit();
// note: with no-cors, response is opaque and ok will always be false
if (!response.ok && (msg.options.mode !== 'no-cors')) {
console.error("RESPONSE NOT OK", msg.url, response);
}
}).catch (err => {
//check_exit();
console.error("REQUEST ERROR", msg.url, err);
});
});
</script>
</head>
<body>
</body>
</html>

24
site/serve.json Normal file
View File

@@ -0,0 +1,24 @@
{
"headers": [
{
"source": "**",
"headers": [{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
}, {
"key": "Cross-Origin-Embedder-Policy",
"value": "credentialless"
}]
},
{
"source": "../**",
"headers": [{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
}, {
"key": "Cross-Origin-Embedder-Policy",
"value": "credentialless"
}]
}
]
}

View File

@@ -10,6 +10,7 @@
<script type="module" src="./streamana.js"></script> <script type="module" src="./streamana.js"></script>
</head> </head>
<body class="d-flex flex-column vh-100 d-none"> <body class="d-flex flex-column vh-100 d-none">
<iframe credentialless id="poster" src="poster.html" width=0 height=0></iframe>
<div id="busy" class="busy"></div> <div id="busy" class="busy"></div>
<nav id="nav" class="navbar navbar-light bg-light flex-grow-0"> <nav id="nav" class="navbar navbar-light bg-light flex-grow-0">
<div class="container-fluid"> <div class="container-fluid">
@@ -66,7 +67,7 @@
<label for="zoom-video" class="form-check-label">Minimize vertical bars in local video display</label> <label for="zoom-video" class="form-check-label">Minimize vertical bars in local video display</label>
</div> </div>
<div class="pt-4"> <div class="pt-4">
<label class="form-label">Ingestion protocol</label> <div class="form-label">Ingestion protocol</div>
<div> <div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input id="protocol-hls" name="protocol" class="form-check-input" type="radio" value="ffmpeg-worker-hls.js"> <input id="protocol-hls" name="protocol" class="form-check-input" type="radio" value="ffmpeg-worker-hls.js">
@@ -79,7 +80,7 @@
</div> </div>
</div> </div>
<div class="pt-4"> <div class="pt-4">
<label class="form-label">Request method</label> <div class="form-label">Request method</div>
<div> <div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input id="request-post" name="request-method" class="form-check-input" type="radio" value="POST"> <input id="request-post" name="request-method" class="form-check-input" type="radio" value="POST">
@@ -92,7 +93,7 @@
</div> </div>
</div> </div>
<div class="pt-4"> <div class="pt-4">
<label class="form-label">Cross-Origin Resource Sharing (CORS)</label> <div class="form-label">Cross-Origin Resource Sharing (CORS)</div>
<div> <div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input id="request-cors" name="request-mode" class="form-check-input" type="radio" value="cors"> <input id="request-cors" name="request-mode" class="form-check-input" type="radio" value="cors">
@@ -109,7 +110,7 @@
</div> </div>
</div> </div>
<div class="pt-4"> <div class="pt-4">
<label class="form-label">Encoder Preference</label> <div class="form-label">Encoder Preference</div>
<div> <div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input id="prefer-mediarecorder" name="preferred-encoder" class="form-check-input" type="radio" value="mediarecorder"> <input id="prefer-mediarecorder" name="preferred-encoder" class="form-check-input" type="radio" value="mediarecorder">

View File

@@ -157,6 +157,8 @@ if (localStorage.getItem('streamana-encoder-preference') === 'webcodecs') {
prefer_mediarecorder_el.checked = true; prefer_mediarecorder_el.checked = true;
} }
const poster_el = document.getElementById('poster');
let streamer_config; let streamer_config;
let video_config; let video_config;
const video_configs = new Map(); const video_configs = new Map();
@@ -267,7 +269,7 @@ await set_ingestion();
document.body.classList.remove('d-none'); document.body.classList.remove('d-none');
document.documentElement.classList.remove('busy'); document.documentElement.classList.remove('busy');
let streamer; let streamer, audio_context;
async function start() { async function start() {
const ingestion_url = ingestion_url_el.value.trim(); const ingestion_url = ingestion_url_el.value.trim();
@@ -338,7 +340,7 @@ async function start() {
const zoom_video = zoom_video_el.checked; const zoom_video = zoom_video_el.checked;
const lock_portrait = /*screen.orientation.type.startsWith('portrait') &&*/ lock_portrait_el.checked; const lock_portrait = /*screen.orientation.type.startsWith('portrait') &&*/ lock_portrait_el.checked;
let audio_context, video_el, video_track, silence, audio_source, audio_dest, gl_canvas, canvas_stream, done = false; let video_el, video_track, silence, audio_source, audio_dest, gl_canvas, canvas_stream, done = false;
function cleanup(err) { function cleanup(err) {
if (err) { if (err) {
@@ -397,9 +399,8 @@ async function start() {
silence.stop(); silence.stop();
} }
if (audio_context) { if (audio_context) {
audio_context.suspend().then(function () { // Chrome doesn't GC AudioContexts so reuse a single instance.
audio_context.close(); audio_context.suspend();
});;
} }
if (video_track) { if (video_track) {
video_track.stop(); video_track.stop();
@@ -652,10 +653,19 @@ async function start() {
} }
try { try {
// Safari requires us to create and resume an AudioContext in the click handler if (!audio_context) {
// and doesn't track async calls. audio_context = new AudioContext();
audio_context = new AudioContext(); }
audio_context.resume();
try {
audio_context.resume();
} catch {
// Safari requires us to create and resume an AudioContext
// in the click handler and doesn't track async calls.
audio_context.close();
audio_context = new AudioContext();
audio_context.resume();
}
// create video element which will be used for grabbing the frames to // create video element which will be used for grabbing the frames to
// write to a canvas so we can apply webgl shaders // write to a canvas so we can apply webgl shaders
@@ -755,7 +765,8 @@ async function start() {
streamer_config, streamer_config,
lock_portrait, lock_portrait,
{ method, mode }, { method, mode },
prefer_webcodecs_el.checked); prefer_webcodecs_el.checked,
poster_el.contentWindow);
streamer.addEventListener('run', () => console.log('Streamer running')); streamer.addEventListener('run', () => console.log('Streamer running'));
streamer.addEventListener('exit', ev => { streamer.addEventListener('exit', ev => {
const msg = `Streamer exited with status ${ev.detail.code}`; const msg = `Streamer exited with status ${ev.detail.code}`;

View File

@@ -60,7 +60,7 @@ export function get_default_config_from_url(ffmpeg_lib_url) {
} }
export class Streamer extends EventTarget { export class Streamer extends EventTarget {
constructor(stream, audio_context, base_url, config, rotate, request_options, prefer_webcodecs) { constructor(stream, audio_context, base_url, config, rotate, request_options, prefer_webcodecs, poster) {
super(); super();
this.stream = stream; this.stream = stream;
this.audio_context = audio_context; this.audio_context = audio_context;
@@ -76,6 +76,7 @@ export class Streamer extends EventTarget {
this.sending = false; this.sending = false;
this.started = false; this.started = false;
this.prefer_webcodecs = prefer_webcodecs; this.prefer_webcodecs = prefer_webcodecs;
this.poster = poster;
} }
async start() { async start() {
@@ -168,7 +169,7 @@ export class Streamer extends EventTarget {
this.config.ffmpeg.audio.codec, // re-encode audio this.config.ffmpeg.audio.codec, // re-encode audio
'-b:a', this.config.audio.bitrate.toString() // set audio bitrate '-b:a', this.config.audio.bitrate.toString() // set audio bitrate
], ],
base_url: this.base_url, base_url: 'postMessage:', //this.base_url,
protocol: this.config.protocol, protocol: this.config.protocol,
protocol_args: [], protocol_args: [],
request_options: this.request_options request_options: this.request_options
@@ -295,6 +296,11 @@ export class Streamer extends EventTarget {
} }
this.dispatchEvent(new CustomEvent(msg.type, { detail: { code: msg.code } })); this.dispatchEvent(new CustomEvent(msg.type, { detail: { code: msg.code } }));
break; break;
case 'upload':
msg.url = this.base_url + msg.url.split(':')[1];
this.poster.postMessage(msg, '*', msg.transfer);
break;
} }
}); });
} }
@@ -397,6 +403,11 @@ export class Streamer extends EventTarget {
} }
this.dispatchEvent(new CustomEvent(msg.type, { detail: { code: msg.code } })); this.dispatchEvent(new CustomEvent(msg.type, { detail: { code: msg.code } }));
break; break;
case 'upload':
msg.url = this.base_url + msg.url.split(':')[1];
this.poster.postMessage(msg, '*', msg.transfer);
break;
} }
}; };
@@ -457,7 +468,7 @@ export class Streamer extends EventTarget {
this.worker.postMessage({ this.worker.postMessage({
type: 'start', type: 'start',
webm_metadata: { webm_metadata: {
max_segment_duration: BigInt(1000000000), max_cluster_duration: BigInt(1000000000),
video: { video: {
width: video_settings.width, width: video_settings.width,
height: video_settings.height, height: video_settings.height,