Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

154 lines
3.7 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
#video {
width: 100%;
height: 100%;
background: black;
}
</style>
</head>
<body>
<script src="hls.min.js"></script>
<script>
const create = (video) => {
// always prefer hls.js over native HLS.
// this is because some Android versions support native HLS
// but don't support fMP4s.
if (Hls.isSupported()) {
const hls = new Hls({
maxLiveSyncPlaybackRate: 1.5,
});
hls.on(Hls.Events.ERROR, (evt, data) => {
if (data.type === Hls.ErrorTypes.MEDIA_ERROR)
hls.recoverMediaError();
else if (data.fatal) {
hls.destroy();
setTimeout(() => create(video), 2000);
}
});
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.loadSource('index.m3u8' + window.location.search);
});
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play();
});
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// since it's not possible to detect timeout errors in iOS,
// wait for the playlist to be available before starting the stream
fetch('index.m3u8')
.then(() => {
video.src = 'index.m3u8';
video.play();
});
}
};
/**
* Parses the query string from a URL into an object representing the query parameters.
* If no URL is provided, it uses the query string from the current page's URL.
*
* @param {string} [url=window.location.search] - The URL to parse the query string from.
* @returns {Object} An object representing the query parameters with keys as parameter names and values as parameter values.
*/
const parseQueryString = (url) => {
const queryString = (url || window.location.search).split("?")[1];
if (!queryString) return {};
const paramsArray = queryString.split("&");
const result = {};
for (let i = 0; i < paramsArray.length; i++) {
const param = paramsArray[i].split("=");
const key = decodeURIComponent(param[0]);
const value = decodeURIComponent(param[1] || "");
if (key) {
if (result[key]) {
if (Array.isArray(result[key])) {
result[key].push(value);
} else {
result[key] = [result[key], value];
}
} else {
result[key] = value;
}
}
}
return result;
};
/**
* Parses a string with boolean-like values and returns a boolean.
* @param {string} str The string to parse
* @param {boolean} defaultVal The default value
* @returns {boolean}
*/
const parseBoolString = (str, defaultVal) => {
const trueValues = ["1", "yes", "true"];
const falseValues = ["0", "no", "false"];
str = (str || "").toString();
if (trueValues.includes(str.toLowerCase())) {
return true;
} else if (falseValues.includes(str.toLowerCase())) {
return false;
} else {
return defaultVal;
}
};
/**
* Sets video attributes based on query string parameters or default values.
*
* @param {HTMLVideoElement} video - The video element on which to set the attributes.
*/
const setVideoAttributes = (video) => {
let qs = parseQueryString();
video.controls = parseBoolString(qs["controls"], true);
video.muted = parseBoolString(qs["muted"], true);
video.autoplay = parseBoolString(qs["autoplay"], true);
video.playsInline = parseBoolString(qs["playsinline"], true);
};
/**
*
* @param {(video: HTMLVideoElement) => void} callback
* @param {HTMLElement} container
* @returns
*/
const initVideoElement = (callback, container) => {
return () => {
const video = document.createElement("video");
video.id = "video";
setVideoAttributes(video);
container.append(video);
callback(video);
};
};
window.addEventListener('DOMContentLoaded', initVideoElement(create, document.body));
</script>
</body>
</html>