mirror of
https://github.com/davedoesdev/streamana.git
synced 2025-09-26 17:51:12 +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,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' },
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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">
|
||||
|
@@ -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} — ${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} — ${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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user