golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
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.
156 lines
3.7 KiB
156 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>
|
|
|