More DASH fixes

This commit is contained in:
David Halls
2021-10-16 07:37:09 +01:00
parent 4e23f98a0f
commit 582f14f7e9
8 changed files with 135 additions and 82 deletions

1
site/ffmpeg-worker-dash.js Symbolic link
View File

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

View File

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

View File

@@ -21,13 +21,22 @@ export class MuxReceiver extends EventTarget {
'-loglevel', 'debug',
'-seekable', '0',
...ffmpeg_args,
'-f', 'hls', // use hls encoder
'-hls_time', '2', // 2 second HLS chunks
'-hls_segment_type', 'mpegts', // MPEG2-TS muxer
'-hls_list_size', '2', // two chunks in the list at a time
'-hls_flags', 'split_by_time',
'/outbound/output.m3u8' // path to media playlist file in virtual FS,
// must be under /outbound
...(protocol === 'dash' ? [
'-f', 'dash', // use dash encoder
'-seg_duration', '2', // 2 second segments
'-window_size', '2',
'-streaming', '1',
'-dash_segment_type', 'webm',
'/outbound/output.mpd'
] : [
'-f', 'hls', // use hls encoder
'-hls_time', '2', // 2 second HLS chunks
'-hls_segment_type', 'mpegts', // MPEG2-TS muxer
'-hls_list_size', '2', // two chunks in the list at a time
'-hls_flags', 'split_by_time',
'/outbound/output.m3u8' // path to media playlist file in virtual FS,
// must be under /outbound
])
],
MEMFS: [
{ name: 'stream1' },

View File

@@ -32,3 +32,16 @@ canvas.zoom {
.camera-icon.off {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M10.961 12.365a1.99 1.99 0 0 0 .522-1.103l3.11 1.382A1 1 0 0 0 16 11.731V4.269a1 1 0 0 0-1.406-.913l-3.111 1.382A2 2 0 0 0 9.5 3H4.272l.714 1H9.5a1 1 0 0 1 1 1v6a1 1 0 0 1-.144.518l.605.847zM1.428 4.18A.999.999 0 0 0 1 5v6a1 1 0 0 0 1 1h5.014l.714 1H2a2 2 0 0 1-2-2V5c0-.675.334-1.272.847-1.634l.58.814zM15 11.73l-3.5-1.555v-4.35L15 4.269v7.462zm-4.407 3.56-10-14 .814-.58 10 14-.814.58z'/%3E%3C/svg%3E") !important;
}
#busy {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
.busy {
cursor: wait;
}

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html class="busy">
<head>
<title>Streamana</title>
<meta charset="utf-8">
@@ -9,7 +9,8 @@
<link href="./streamana.css" rel="stylesheet">
<script type="module" src="./streamana.js"></script>
</head>
<body class="d-flex flex-column vh-100">
<body class="d-flex flex-column vh-100 d-none">
<div id="busy" class="busy"></div>
<nav id="nav" class="navbar navbar-light bg-light flex-grow-0">
<div class="container-fluid">
<div class="row gx-2 gy-2 gy-sm-0 w-100 mx-0 align-items-center">

View File

@@ -9,9 +9,6 @@ import {
max_video_config,
} from './resolution.js';
const ingestion_url_el = document.getElementById('ingestion-url');
ingestion_url_el.value = localStorage.getItem('streamana-ingestion-url');
const go_live_el = document.getElementById('go-live');
go_live_el.disabled = false;
go_live_el.addEventListener('click', function () {
@@ -37,15 +34,7 @@ if (initial_ffmpeg_lib_url) {
ffmpeg_lib_url_el.value = initial_ffmpeg_lib_url;
}
ffmpeg_lib_url_el.addEventListener('change', function () {
const value = this.value.trim();
localStorage.setItem('streamana-ffmpeg-lib-url', value);
if (value) {
protocol_hls_el.disabled = true;
protocol_dash_el.disabled = true;
} else {
protocol_hls_el.disabled = false;
protocol_dash_el.disabled = false;
}
localStorage.setItem('streamana-ffmpeg-lib-url', this.value.trim());
set_ingestion();
});
@@ -112,6 +101,7 @@ camera_el.addEventListener('click', camera_save);
const camera_swap_el = document.getElementById('camera-swap');
const ingestion_url_el = document.getElementById('ingestion-url');
const protocol_hls_el = document.getElementById('protocol-hls');
const protocol_dash_el = document.getElementById('protocol-dash');
const resolution_el = document.getElementById('resolution');
@@ -134,49 +124,6 @@ function set_ingestion_protocol(protocol) {
set_ingestion_protocol(localStorage.getItem('streamana-ingestion-protocol'));
async function set_ingestion() {
const ffmpeg_lib_url = ffmpeg_lib_url_el.value.trim() ||
ffmpeg_lib_url_el.placeholder.trim();
streamer_config = get_default_config_from_url(ffmpeg_lib_url);
set_ingestion_protocol(streamer_config.protocol);
localStorage.setItem('streamana-ingestion-protocol', streamer_config.protocol);
video_config = null;
let preferred_resolution = localStorage.getItem('streamana-resolution');
if (preferred_resolution) {
video_config = await max_video_config({
...JSON.parse(preferred_resolution),
...streamer_config.video,
...streamer_config.webcodecs.video
}, true);
}
if (!video_config) {
video_config = await max_video_config({
width: 1280,
height: 720,
ratio: 16/9,
...streamer_config.video,
...streamer_config.webcodecs.video
}, true);
}
resolution_el.innerHTML = '';
for (let config of (await supported_video_configs({
...streamer_config.video,
...streamer_config.webcodecs.video
}, true)).filter(c => c.ratio >= 1)) {
const option = document.createElement('option');
option.innerHTML = `${config.width}x${config.height} &mdash; ${config.label}`;
option.selected = config.label === video_config.label;
resolution_el.appendChild(option);
video_configs.set(option.innerText, config);
}
}
await set_ingestion();
protocol_hls_el.addEventListener('change', function () {
ffmpeg_lib_url_el.placeholder = protocol_hls_el.value;
set_ingestion();
@@ -196,8 +143,86 @@ resolution_el.addEventListener('change', function () {
}));
});
const busy_el = document.getElementById('busy');
async function set_ingestion() {
busy_el.classList.remove('d-none');
try {
const ffmpeg_lib_url = ffmpeg_lib_url_el.value.trim() ||
ffmpeg_lib_url_el.placeholder.trim();
const protocol = streamer_config ? streamer_config.protocol : null;
streamer_config = get_default_config_from_url(ffmpeg_lib_url);
set_ingestion_protocol(streamer_config.protocol);
localStorage.setItem('streamana-ingestion-protocol', streamer_config.protocol);
if (ffmpeg_lib_url_el.value.trim()) {
protocol_hls_el.disabled = true;
protocol_dash_el.disabled = true;
} else {
protocol_hls_el.disabled = false;
protocol_dash_el.disabled = false;
}
if (streamer_config.protocol !== protocol) {
ingestion_url_el.value = (localStorage.getItem(
streamer_config.protocol === 'dash' ?
'streamana-dash-ingestion-url' :
'streamana-hls-ingestion-url') || '').trim();
}
video_config = null;
let preferred_resolution = localStorage.getItem('streamana-resolution');
if (preferred_resolution) {
video_config = await max_video_config({
...JSON.parse(preferred_resolution),
...streamer_config.video,
...streamer_config.webcodecs.video
}, true);
}
if (!video_config) {
video_config = await max_video_config({
width: 1280,
height: 720,
ratio: 16/9,
...streamer_config.video,
...streamer_config.webcodecs.video
}, true);
}
const configs = (await supported_video_configs({
...streamer_config.video,
...streamer_config.webcodecs.video
}, true)).filter(c => c.ratio >= 1);
resolution_el.innerHTML = '';
for (let config of configs) {
const option = document.createElement('option');
option.innerHTML = `${config.width}x${config.height} &mdash; ${config.label}`;
option.selected = video_config && (config.label === video_config.label);
resolution_el.appendChild(option);
video_configs.set(option.innerText, config);
}
return streamer_config;
} finally {
busy_el.classList.add('d-none');
}
}
await set_ingestion();
document.body.classList.remove('d-none');
document.documentElement.classList.remove('busy');
let streamer;
// ingestion url doesn't change when protocol changes due to ffmpeg_lib_url
// why does clicking on menu close the menu after change ffmpeg_lib_url?
async function start() {
const ingestion_url = ingestion_url_el.value.trim();
if (!ingestion_url) {
@@ -205,7 +230,10 @@ async function start() {
go_live_el.checked = false;
return;
}
localStorage.setItem('streamana-ingestion-url', ingestion_url);
localStorage.setItem(
streamer_config.protocol === 'dash' ? 'streamana-dash-ingestion-url' :
'streamana-hls-ingestion-url',
ingestion_url);
if (!video_config) {
console.error('No video config');
@@ -251,7 +279,7 @@ async function start() {
function cleanup(err) {
if (err) {
console.error(err.toString());
console.error(err);
}
if (done) {
return;
@@ -445,7 +473,7 @@ async function start() {
});
} catch (ex) {
console.warn(`Failed to get user media (need_audio=${need_audio} need_video=${need_video})`);
console.error(ex.toString());
console.error(ex);
if (need_audio && need_video) {
console.warn("Retrying with only video");
try {
@@ -455,7 +483,7 @@ async function start() {
});
} catch (ex) {
console.warn('Failed to get user video, retrying with only audio');
console.error(ex.toString());
console.error(ex);
try {
media_stream = await navigator.mediaDevices.getUserMedia({
audio: true,
@@ -463,7 +491,7 @@ async function start() {
});
} catch (ex) {
console.warn('Failed to get user audio');
console.error(ex.toString());
console.error(ex);
}
}
}

View File

@@ -102,7 +102,7 @@ export class Streamer extends EventTarget {
await this.webcodecs();
console.log("Using WebCodecs");
} catch (ex) {
console.warn(ex.toString());
console.warn(ex);
await mp4();
}
} else {
@@ -117,7 +117,7 @@ export class Streamer extends EventTarget {
await this.media_recorder(`video/webm;codecs=${codecs}`);
console.log(`Using MediaRecorder WebM (${codecs})`);
} catch (ex) {
console.warn(ex.toString());
console.warn(ex);
await webcodecs();
}
} else {
@@ -153,17 +153,17 @@ export class Streamer extends EventTarget {
'-i', '/work/stream1',
'-map', '0:v',
'-map', '0:a',
...(video_codec === this.config.ffmpeg.video.codec ||
video_codec === 'copy' ?
['-c:v', 'copy'] : // pass through the video data (no decoding or encoding)
['-c:v', this.config.ffmpeg.video.codec, // re-encode video
'-b:v', this.config.video.bitrate.toString()]), // set video bitrate
'-c:v', video_codec === this.config.ffmpeg.video.codec ||
video_codec === 'copy' ?
'copy' : // pass through the video data (no decoding or encoding)
this.config.ffmpeg.video.codec, // re-encode video
'-b:v', this.config.video.bitrate.toString(), // set video bitrate
...this.ffmpeg_metadata,
...(audio_codec === this.config.ffmpeg.audio.codec ||
audio_codec === 'copy' ?
['-c:a', 'copy'] : // pass through the audio data
['-c:a', this.config.ffmpeg.audio.codec, // re-encode audio
'-b:a', this.config.audio.bitrate.toString()]) // set audio bitrate
'-c:a', audio_codec === this.config.ffmpeg.audio.codec ||
audio_codec === 'copy' ?
'copy' : // pass through the audio data
this.config.ffmpeg.audio.codec, // re-encode audio
'-b:a', this.config.audio.bitrate.toString() // set audio bitrate
],
base_url: this.base_url,
protocol: this.config.protocol