mirror of
https://github.com/davedoesdev/streamana.git
synced 2025-10-05 13:36:50 +08:00
More DASH fixes
This commit is contained in:
Submodule ffmpeg.js updated: d23484b1ef...f5f0d72090
1
site/ffmpeg-worker-dash.js
Symbolic link
1
site/ffmpeg-worker-dash.js
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../ffmpeg.js/ffmpeg-worker-dash.js
|
1
site/ffmpeg-worker-dash.wasm
Symbolic link
1
site/ffmpeg-worker-dash.wasm
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../ffmpeg.js/ffmpeg-worker-dash.wasm
|
@@ -21,6 +21,14 @@ export class MuxReceiver extends EventTarget {
|
|||||||
'-loglevel', 'debug',
|
'-loglevel', 'debug',
|
||||||
'-seekable', '0',
|
'-seekable', '0',
|
||||||
...ffmpeg_args,
|
...ffmpeg_args,
|
||||||
|
...(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
|
'-f', 'hls', // use hls encoder
|
||||||
'-hls_time', '2', // 2 second HLS chunks
|
'-hls_time', '2', // 2 second HLS chunks
|
||||||
'-hls_segment_type', 'mpegts', // MPEG2-TS muxer
|
'-hls_segment_type', 'mpegts', // MPEG2-TS muxer
|
||||||
@@ -28,6 +36,7 @@ export class MuxReceiver extends EventTarget {
|
|||||||
'-hls_flags', 'split_by_time',
|
'-hls_flags', 'split_by_time',
|
||||||
'/outbound/output.m3u8' // path to media playlist file in virtual FS,
|
'/outbound/output.m3u8' // path to media playlist file in virtual FS,
|
||||||
// must be under /outbound
|
// must be under /outbound
|
||||||
|
])
|
||||||
],
|
],
|
||||||
MEMFS: [
|
MEMFS: [
|
||||||
{ name: 'stream1' },
|
{ name: 'stream1' },
|
||||||
|
@@ -32,3 +32,16 @@ canvas.zoom {
|
|||||||
.camera-icon.off {
|
.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;
|
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;
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html class="busy">
|
||||||
<head>
|
<head>
|
||||||
<title>Streamana</title>
|
<title>Streamana</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
<link href="./streamana.css" rel="stylesheet">
|
<link href="./streamana.css" rel="stylesheet">
|
||||||
<script type="module" src="./streamana.js"></script>
|
<script type="module" src="./streamana.js"></script>
|
||||||
</head>
|
</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">
|
<nav id="nav" class="navbar navbar-light bg-light flex-grow-0">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row gx-2 gy-2 gy-sm-0 w-100 mx-0 align-items-center">
|
<div class="row gx-2 gy-2 gy-sm-0 w-100 mx-0 align-items-center">
|
||||||
|
@@ -9,9 +9,6 @@ import {
|
|||||||
max_video_config,
|
max_video_config,
|
||||||
} from './resolution.js';
|
} 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');
|
const go_live_el = document.getElementById('go-live');
|
||||||
go_live_el.disabled = false;
|
go_live_el.disabled = false;
|
||||||
go_live_el.addEventListener('click', function () {
|
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.value = initial_ffmpeg_lib_url;
|
||||||
}
|
}
|
||||||
ffmpeg_lib_url_el.addEventListener('change', function () {
|
ffmpeg_lib_url_el.addEventListener('change', function () {
|
||||||
const value = this.value.trim();
|
localStorage.setItem('streamana-ffmpeg-lib-url', 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;
|
|
||||||
}
|
|
||||||
set_ingestion();
|
set_ingestion();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,6 +101,7 @@ camera_el.addEventListener('click', camera_save);
|
|||||||
|
|
||||||
const camera_swap_el = document.getElementById('camera-swap');
|
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_hls_el = document.getElementById('protocol-hls');
|
||||||
const protocol_dash_el = document.getElementById('protocol-dash');
|
const protocol_dash_el = document.getElementById('protocol-dash');
|
||||||
const resolution_el = document.getElementById('resolution');
|
const resolution_el = document.getElementById('resolution');
|
||||||
@@ -134,15 +124,55 @@ function set_ingestion_protocol(protocol) {
|
|||||||
|
|
||||||
set_ingestion_protocol(localStorage.getItem('streamana-ingestion-protocol'));
|
set_ingestion_protocol(localStorage.getItem('streamana-ingestion-protocol'));
|
||||||
|
|
||||||
|
protocol_hls_el.addEventListener('change', function () {
|
||||||
|
ffmpeg_lib_url_el.placeholder = protocol_hls_el.value;
|
||||||
|
set_ingestion();
|
||||||
|
});
|
||||||
|
|
||||||
|
protocol_dash_el.addEventListener('change', function () {
|
||||||
|
ffmpeg_lib_url_el.placeholder = protocol_dash_el.value;
|
||||||
|
set_ingestion();
|
||||||
|
});
|
||||||
|
|
||||||
|
resolution_el.addEventListener('change', function () {
|
||||||
|
video_config = video_configs.get(this.value);
|
||||||
|
localStorage.setItem('streamana-resolution', JSON.stringify({
|
||||||
|
width: video_config.width,
|
||||||
|
height: video_config.height,
|
||||||
|
ratio: video_config.ratio
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const busy_el = document.getElementById('busy');
|
||||||
|
|
||||||
async function set_ingestion() {
|
async function set_ingestion() {
|
||||||
|
busy_el.classList.remove('d-none');
|
||||||
|
|
||||||
|
try {
|
||||||
const ffmpeg_lib_url = ffmpeg_lib_url_el.value.trim() ||
|
const ffmpeg_lib_url = ffmpeg_lib_url_el.value.trim() ||
|
||||||
ffmpeg_lib_url_el.placeholder.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);
|
streamer_config = get_default_config_from_url(ffmpeg_lib_url);
|
||||||
|
|
||||||
set_ingestion_protocol(streamer_config.protocol);
|
set_ingestion_protocol(streamer_config.protocol);
|
||||||
localStorage.setItem('streamana-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;
|
video_config = null;
|
||||||
let preferred_resolution = localStorage.getItem('streamana-resolution');
|
let preferred_resolution = localStorage.getItem('streamana-resolution');
|
||||||
if (preferred_resolution) {
|
if (preferred_resolution) {
|
||||||
@@ -162,42 +192,37 @@ async function set_ingestion() {
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolution_el.innerHTML = '';
|
const configs = (await supported_video_configs({
|
||||||
for (let config of (await supported_video_configs({
|
|
||||||
...streamer_config.video,
|
...streamer_config.video,
|
||||||
...streamer_config.webcodecs.video
|
...streamer_config.webcodecs.video
|
||||||
}, true)).filter(c => c.ratio >= 1)) {
|
}, true)).filter(c => c.ratio >= 1);
|
||||||
|
|
||||||
|
resolution_el.innerHTML = '';
|
||||||
|
|
||||||
|
for (let config of configs) {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.innerHTML = `${config.width}x${config.height} — ${config.label}`;
|
option.innerHTML = `${config.width}x${config.height} — ${config.label}`;
|
||||||
option.selected = config.label === video_config.label;
|
option.selected = video_config && (config.label === video_config.label);
|
||||||
resolution_el.appendChild(option);
|
resolution_el.appendChild(option);
|
||||||
video_configs.set(option.innerText, config);
|
video_configs.set(option.innerText, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return streamer_config;
|
||||||
|
} finally {
|
||||||
|
busy_el.classList.add('d-none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await set_ingestion();
|
await set_ingestion();
|
||||||
|
|
||||||
protocol_hls_el.addEventListener('change', function () {
|
document.body.classList.remove('d-none');
|
||||||
ffmpeg_lib_url_el.placeholder = protocol_hls_el.value;
|
document.documentElement.classList.remove('busy');
|
||||||
set_ingestion();
|
|
||||||
});
|
|
||||||
|
|
||||||
protocol_dash_el.addEventListener('change', function () {
|
|
||||||
ffmpeg_lib_url_el.placeholder = protocol_dash_el.value;
|
|
||||||
set_ingestion();
|
|
||||||
});
|
|
||||||
|
|
||||||
resolution_el.addEventListener('change', function () {
|
|
||||||
video_config = video_configs.get(this.value);
|
|
||||||
localStorage.setItem('streamana-resolution', JSON.stringify({
|
|
||||||
width: video_config.width,
|
|
||||||
height: video_config.height,
|
|
||||||
ratio: video_config.ratio
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
let streamer;
|
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() {
|
async function start() {
|
||||||
const ingestion_url = ingestion_url_el.value.trim();
|
const ingestion_url = ingestion_url_el.value.trim();
|
||||||
if (!ingestion_url) {
|
if (!ingestion_url) {
|
||||||
@@ -205,7 +230,10 @@ async function start() {
|
|||||||
go_live_el.checked = false;
|
go_live_el.checked = false;
|
||||||
return;
|
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) {
|
if (!video_config) {
|
||||||
console.error('No video config');
|
console.error('No video config');
|
||||||
@@ -251,7 +279,7 @@ async function start() {
|
|||||||
|
|
||||||
function cleanup(err) {
|
function cleanup(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err.toString());
|
console.error(err);
|
||||||
}
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
return;
|
return;
|
||||||
@@ -445,7 +473,7 @@ async function start() {
|
|||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn(`Failed to get user media (need_audio=${need_audio} need_video=${need_video})`);
|
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) {
|
if (need_audio && need_video) {
|
||||||
console.warn("Retrying with only video");
|
console.warn("Retrying with only video");
|
||||||
try {
|
try {
|
||||||
@@ -455,7 +483,7 @@ async function start() {
|
|||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn('Failed to get user video, retrying with only audio');
|
console.warn('Failed to get user video, retrying with only audio');
|
||||||
console.error(ex.toString());
|
console.error(ex);
|
||||||
try {
|
try {
|
||||||
media_stream = await navigator.mediaDevices.getUserMedia({
|
media_stream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: true,
|
audio: true,
|
||||||
@@ -463,7 +491,7 @@ async function start() {
|
|||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn('Failed to get user audio');
|
console.warn('Failed to get user audio');
|
||||||
console.error(ex.toString());
|
console.error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -102,7 +102,7 @@ export class Streamer extends EventTarget {
|
|||||||
await this.webcodecs();
|
await this.webcodecs();
|
||||||
console.log("Using WebCodecs");
|
console.log("Using WebCodecs");
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn(ex.toString());
|
console.warn(ex);
|
||||||
await mp4();
|
await mp4();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +117,7 @@ export class Streamer extends EventTarget {
|
|||||||
await this.media_recorder(`video/webm;codecs=${codecs}`);
|
await this.media_recorder(`video/webm;codecs=${codecs}`);
|
||||||
console.log(`Using MediaRecorder WebM (${codecs})`);
|
console.log(`Using MediaRecorder WebM (${codecs})`);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn(ex.toString());
|
console.warn(ex);
|
||||||
await webcodecs();
|
await webcodecs();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -153,17 +153,17 @@ export class Streamer extends EventTarget {
|
|||||||
'-i', '/work/stream1',
|
'-i', '/work/stream1',
|
||||||
'-map', '0:v',
|
'-map', '0:v',
|
||||||
'-map', '0:a',
|
'-map', '0:a',
|
||||||
...(video_codec === this.config.ffmpeg.video.codec ||
|
'-c:v', video_codec === this.config.ffmpeg.video.codec ||
|
||||||
video_codec === 'copy' ?
|
video_codec === 'copy' ?
|
||||||
['-c:v', 'copy'] : // pass through the video data (no decoding or encoding)
|
'copy' : // pass through the video data (no decoding or encoding)
|
||||||
['-c:v', this.config.ffmpeg.video.codec, // re-encode video
|
this.config.ffmpeg.video.codec, // re-encode video
|
||||||
'-b:v', this.config.video.bitrate.toString()]), // set video bitrate
|
'-b:v', this.config.video.bitrate.toString(), // set video bitrate
|
||||||
...this.ffmpeg_metadata,
|
...this.ffmpeg_metadata,
|
||||||
...(audio_codec === this.config.ffmpeg.audio.codec ||
|
'-c:a', audio_codec === this.config.ffmpeg.audio.codec ||
|
||||||
audio_codec === 'copy' ?
|
audio_codec === 'copy' ?
|
||||||
['-c:a', 'copy'] : // pass through the audio data
|
'copy' : // pass through the audio data
|
||||||
['-c:a', 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: this.base_url,
|
||||||
protocol: this.config.protocol
|
protocol: this.config.protocol
|
||||||
|
Reference in New Issue
Block a user