Skip to content

Commit

Permalink
Use hls-js for HLS in WebUI
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlt8 committed Jun 13, 2024
1 parent 42a9ee2 commit 9d6a1be
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 56 deletions.
18 changes: 9 additions & 9 deletions app/static/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ video {
top: 0;
}

[data-enabled=True] video.webrtc.placeholder,
[data-enabled=True] video.webrtc.placeholder+i,
[data-enabled=True] video.placeholder,
[data-enabled=True] video.placeholder+i,
.status .fa-circle-play,
.status .fa-circle-pause,
.status .fa-satellite-dish,
Expand All @@ -148,17 +148,17 @@ video {
}

video,
video.webrtc+.fas {
video+.fas {
display: block;
}

figure.image:hover>video.webrtc.placeholder {
figure.image:hover>video.placeholder {
transition: .3s ease;
background-color: transparent;
opacity: 0.8;
}

video.webrtc+i::before {
video+i::before {
color: white;
font-size: 4rem;
position: absolute;
Expand All @@ -168,17 +168,17 @@ video.webrtc+i::before {
text-shadow: 2px 2px #444;
}

[data-enabled=True] video.webrtc.placeholder+i::before {
[data-enabled=True] video.placeholder+i::before {
content: "\f144";
cursor: pointer;
}

video.webrtc.lost+i::before {
video.lost+i::before {
content: "\e560";
}

video.webrtc.lost,
video.webrtc.lost+i {
video.lost,
video.lost+i {
cursor: default;
}

Expand Down
63 changes: 38 additions & 25 deletions app/static/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ document.addEventListener("DOMContentLoaded", () => {

function applyPreferences() {
const repeatNumber = getCookie("number_of_columns", 2);
console.debug("applyPreferences number_of_columns", repeatNumber);
const grid = document.querySelectorAll(".camera");
for (var i = 0, len = grid.length; i < len; i++) {
grid[i].classList.forEach((item) => {
Expand All @@ -72,7 +71,6 @@ function applyPreferences() {
sortOrder = sortOrder.replaceAll("%2C", ",")
setCookie("camera_order", sortOrder);
}
console.debug("applyPreferences camera_order", sortOrder);
const ids = sortOrder.split(",");
var cameras = [...document.querySelectorAll(".camera")];
for (var i = 0; i < Math.min(ids.length, cameras.length); i++) {
Expand Down Expand Up @@ -189,7 +187,6 @@ async function update_img(oldUrl, useImg = false) {
if (useImg || ext == "svg") {
newUrl = "img/" + cam + "." + ext;
}
console.debug("update_img", oldUrl, newUrl);
let button = document.querySelector(`.update-preview[data-cam="${cam}"]`);
if (button) {
button.disabled = true;
Expand All @@ -211,20 +208,11 @@ async function update_img(oldUrl, useImg = false) {
});

// update video.poster
document
.querySelectorAll(`[poster="${oldUrl}"],[poster="${newUrl}"]`)
document.querySelectorAll(`[poster="${oldUrl}"],[poster="${newUrl}"]`)
.forEach(function (e) {
e.setAttribute("poster", newUrl);
});

// update video js div for poster
document
.querySelectorAll(
`[style='background-image: url("${oldUrl}");'],[style='background-image: url("${newUrl}");']`
)
.forEach(function (e) {
e.style = `background-image: url("${newUrl}");`;
});
if (button) {
button.disabled = false;
button.getElementsByClassName("fas")[0].classList.remove("fa-spin");
Expand All @@ -237,9 +225,8 @@ async function update_img(oldUrl, useImg = false) {
}

function refresh_imgs() {
console.debug("refresh_imgs " + Date.now());
document.querySelectorAll(".refresh_img").forEach(async function (image) {
let url = image.getAttribute("src");
let url = image.getAttribute(image.nodeName === "IMG" ? "src" : "poster");
if (url === null) { return; }
let CameraBattery = document.getElementById(image.dataset.cam).dataset.battery?.toLowerCase() == "true";
let CameraConnected = image.classList.contains("connected");
Expand Down Expand Up @@ -383,6 +370,7 @@ document.addEventListener("DOMContentLoaded", () => {
icon.classList.remove("fa-plug-circle-exclamation");
icon.classList.add("fa-arrows-rotate");
})
autoplay();
applyPreferences();
});
sse.addEventListener("error", () => {
Expand Down Expand Up @@ -628,7 +616,35 @@ document.addEventListener("DOMContentLoaded", () => {
toggleFullscreen(fs)
})
toggleFullscreen()

function loadHLS(videoElement) {
const videoSrc = `${videoElement.dataset.src}stream.m3u8`;
videoElement.controls = true;
videoElement.classList.remove("placeholder");
if (Hls.isSupported()) {
const hls = new Hls({ maxLiveSyncPlaybackRate: 1.5 });
hls.on(Hls.Events.ERROR, (evt, data) => {
if (data.fatal) { hls.destroy(); }
if (data.type !== Hls.ErrorTypes.NETWORK_ERROR || videoElement.classList.contains("connected")) {
setTimeout(() => loadHLS(videoElement), 2000);
}
});
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
hls.loadSource(videoSrc);
});
hls.attachMedia(videoElement);
} else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
fetch(videoSrc).then(() => { videoElement.src = videoSrc; });
}
}
document.querySelectorAll('video.hls').forEach((videoElement) => {
loadHLS(videoElement);
videoElement.addEventListener('play', () => {
if (!videoElement.classList.contains("connected")) {
fetch(`api/${videoElement.dataset.cam}/start`)
}
videoElement.play()
});
});
// Load WS for WebRTC on demand
function loadWebRTC(video, force = false) {
if (!force && (!video.classList.contains("placeholder") || !video.classList.contains("connected"))) { return }
Expand All @@ -647,23 +663,20 @@ document.addEventListener("DOMContentLoaded", () => {
let videos = document.querySelectorAll('video');
if (action === "stop") {
videos.forEach(video => {
if (video.classList.contains("vjs-tech")) { videojs(video).pause() } else {
video.classList.add("lost");
// show poster on lost connection
}
video.pause();
video.controls = false;
video.classList.add("lost");
});
return;
}
let autoPlay = getCookie("autoplay");
let fullscreen = getCookie("fullscreen");
videos.forEach(video => {
video.controls = true;
video.classList.remove("lost");
if (!autoPlay && !fullscreen && !video.autoplay) { return }
if (video.classList.contains("webrtc")) {
loadWebRTC(video);
} else if (video.classList.contains("vjs-tech")) {
video = videojs(video);
}
if (video.classList.contains("hls")) { loadHLS(video); }
if (video.classList.contains("webrtc")) { loadWebRTC(video); }
video.play();
});
}
Expand Down
27 changes: 7 additions & 20 deletions app/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
{% extends 'base.html' %}

{% block stylesheet %}
{% if show_video %}
<link href="https://cdn.jsdelivr.net/npm/video.js/dist/video-js.min.css" rel="stylesheet" />
{% endif %}
{% endblock %}

{% block content %}
Expand Down Expand Up @@ -165,23 +162,13 @@
<div class="card-image has-text-centered">
<figure class="image">
{% if show_video and (camera.on_demand or camera.enabled) %}
{% if (webrtc and video_format == "webrtc") or (camera.webrtc and video_format == "kvs") %}
<video data-cam="{{cam_uri}}"
class="webrtc loading-preview refresh_img placeholder {{'connected' if camera.connected}}" muted
playsinline {{ "autoplay" if autoplay and camera.connected else "preload=none" }}
poster="{{camera.img_url or 'static/loading.svg'}}"></video>
{% set is_webrtc = webrtc and (video_format == "webrtc" or camera.webrtc and video_format == "kvs") %}
<video data-cam="{{ cam_uri }}" {% if not is_webrtc %}data-src="{{ camera.hls_url }}" {% endif %}
class="{% if is_webrtc %}webrtc{% else %}hls{% endif %} loading-preview refresh_img placeholder {{ 'connected' if camera.connected }}"
muted playsinline {{ "autoplay" if autoplay and camera.connected else "preload=none" }}
poster="{{ camera.img_url or 'static/loading.svg' }}">
</video>
<i class="fas"></i>
{% else %}
<video-js id="video-{{cam_uri}}" class="video-js vjs-live vjs-big-play-centered" controls muted
playsinline preload={{"auto" if autoplay and camera.connected else "none" }}
poster="{{camera.img_url or 'static/loading.svg'}}" data-cam="{{cam_uri}}"
data-setup='{"liveui": true, "fluid": true, "muted": true, "autoplay": {{'"muted"' if autoplay and camera.connected
else "false" }}}' data-src="{{camera.hls_url}}stream.m3u8" width="100%">
<source src="{{camera.hls_url}}stream.m3u8" class="hls-video" type="application/x-mpegURL" />
<img class="refresh_img loading-preview {{'connected' if camera.connected}}"
src="{{camera.img_url or 'static/loading.svg'}}" data-cam="{{cam_uri}}" width="100%" />
</video-js>
{% endif %}
{% elif camera.img_url or camera.connected or (camera.on_demand and camera.enabled) %}
<img class="refresh_img loading-preview {{'connected' if camera.connected}}"
src="{{ camera.img_url or 'static/loading.svg' }}" data-cam="{{cam_uri}}" width="100%" />
Expand Down Expand Up @@ -423,7 +410,7 @@
{% if (webrtc and video_format == "webrtc") or video_format =="kvs" %}
<script src="static/webrtc.js"></script>
{% else %}
<script src="https://cdn.jsdelivr.net/npm/video.js/dist/video.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"></script>
{% endif %}
{% endif %}
{% endblock %}
2 changes: 0 additions & 2 deletions app/wyzebridge/wyze_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,6 @@ def start_tutk_stream(uri: str, stream: StreamTuple, queue: QueueTuple, state: c
else:
logger.warning("Stream stopped")
finally:
if ffmpeg and ffmpeg.poll() is None:
ffmpeg.stdin.close()
state.value = exit_code
stop_and_wait(audio_thread)
stop_and_wait(control_thread)
Expand Down

0 comments on commit 9d6a1be

Please sign in to comment.