mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Improved livestream support
This commit is contained in:
parent
ad9763614c
commit
00ec611ccf
3 changed files with 109 additions and 21 deletions
|
@ -1,5 +1,5 @@
|
|||
import dashjs from 'dashjs';
|
||||
import Hls from 'hls.js';
|
||||
import Hls, { LevelLoadedData } from 'hls.js';
|
||||
import { PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage } from '../Packets';
|
||||
import { Player, PlayerType } from './Player';
|
||||
|
||||
|
@ -23,7 +23,6 @@ function sendPlaybackUpdate(updateState: number) {
|
|||
const updateMessage = new PlaybackUpdateMessage(Date.now(), player.getCurrentTime(), player.getDuration(), updateState, player.getPlaybackRate());
|
||||
|
||||
if (updateMessage.generationTime > lastPlayerUpdateGenerationTime) {
|
||||
console.log(`Sending playback update: ${JSON.stringify(updateMessage)}`);
|
||||
lastPlayerUpdateGenerationTime = updateMessage.generationTime;
|
||||
window.electronAPI.sendPlaybackUpdate(updateMessage);
|
||||
}
|
||||
|
@ -68,6 +67,7 @@ const playerCtrlVolume = document.getElementById("volume");
|
|||
const playerCtrlProgressBar = document.getElementById("progressBar");
|
||||
const playerCtrlProgressBarBuffer = document.getElementById("progressBarBuffer");
|
||||
const playerCtrlProgressBarProgress = document.getElementById("progressBarProgress");
|
||||
const playerCtrlProgressBarPosition = document.getElementById("progressBarPosition");
|
||||
const playerCtrlProgressBarHandle = document.getElementById("progressBarHandle");
|
||||
const PlayerCtrlProgressBarInteractiveArea = document.getElementById("progressBarInteractiveArea");
|
||||
|
||||
|
@ -90,9 +90,14 @@ let playerCtrlSpeedMenuShown = false;
|
|||
|
||||
const playbackRates = ["0.25", "0.50", "0.75", "1.00", "1.25", "1.50", "1.75", "2.00"];
|
||||
const playbackUpdateInterval = 1.0;
|
||||
const livePositionDelta = 5.0;
|
||||
const livePositionWindow = livePositionDelta * 4;
|
||||
let player: Player;
|
||||
let playerPrevTime: number = 0;
|
||||
let lastPlayerUpdateGenerationTime = 0;
|
||||
let isLive = false;
|
||||
let isLivePosition = false;
|
||||
|
||||
|
||||
window.electronAPI.onPlay((_event, value: PlayMessage) => {
|
||||
console.log("Handle play message renderer", value);
|
||||
|
@ -215,12 +220,17 @@ window.electronAPI.onPlay((_event, value: PlayMessage) => {
|
|||
|
||||
const hlsPlayer = new Hls(config);
|
||||
|
||||
hlsPlayer.on(Hls.Events.ERROR, function(eventName, data) {
|
||||
hlsPlayer.on(Hls.Events.ERROR, (eventName, data) => {
|
||||
window.electronAPI.sendPlaybackError({
|
||||
message: `HLS player error: ${JSON.stringify(data)}`
|
||||
});
|
||||
});
|
||||
|
||||
hlsPlayer.on(Hls.Events.LEVEL_LOADED, (eventName, level: LevelLoadedData) => {
|
||||
isLive = level.details.live;
|
||||
isLivePosition = isLive ? true : false;
|
||||
});
|
||||
|
||||
player = new Player(PlayerType.Hls, videoElement, hlsPlayer);
|
||||
|
||||
// value.url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8?ref=developerinsider.co";
|
||||
|
@ -304,8 +314,17 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
|||
playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
|
||||
playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume + 8}px`);
|
||||
|
||||
if (isLive) {
|
||||
playerCtrlLiveBadge.setAttribute("style", "display: block");
|
||||
playerCtrlPosition.setAttribute("style", "display: none");
|
||||
playerCtrlDuration.setAttribute("style", "display: none");
|
||||
}
|
||||
else {
|
||||
playerCtrlLiveBadge.setAttribute("style", "display: none");
|
||||
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
|
||||
playerCtrlDuration.innerHTML = `/  ${formatDuration(player.getDuration())}`;
|
||||
}
|
||||
|
||||
playerCtrlStateUpdate(PlayerControlEvent.SetCaptions);
|
||||
|
||||
break;
|
||||
|
@ -326,6 +345,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
|||
|
||||
case PlayerControlEvent.ToggleMute:
|
||||
player.setMute(!player.isMuted());
|
||||
window.electronAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: 0 });
|
||||
// fallthrough
|
||||
|
||||
case PlayerControlEvent.VolumeChange: {
|
||||
|
@ -350,6 +370,22 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
|||
}
|
||||
|
||||
case PlayerControlEvent.TimeUpdate: {
|
||||
if (isLive) {
|
||||
if (isLivePosition && player.getDuration() - player.getCurrentTime() > livePositionWindow) {
|
||||
isLivePosition = false;
|
||||
playerCtrlLiveBadge.setAttribute("style", `background-color: #595959`);
|
||||
}
|
||||
else if (!isLivePosition && player.getDuration() - player.getCurrentTime() <= livePositionWindow) {
|
||||
isLivePosition = true;
|
||||
playerCtrlLiveBadge.setAttribute("style", `background-color: red`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isLivePosition) {
|
||||
playerCtrlProgressBarProgress.setAttribute("style", `width: ${playerCtrlProgressBar.offsetWidth}px`);
|
||||
playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetWidth + playerCtrlProgressBar.offsetLeft}px`);
|
||||
}
|
||||
else {
|
||||
const buffer = Math.round((player.getBufferLength() / player.getDuration()) * playerCtrlProgressBar.offsetWidth);
|
||||
const progress = Math.round((player.getCurrentTime() / player.getDuration()) * playerCtrlProgressBar.offsetWidth);
|
||||
const handle = progress + playerCtrlProgressBar.offsetLeft;
|
||||
|
@ -359,6 +395,8 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
|||
playerCtrlProgressBarHandle.setAttribute("style", `left: ${handle}px`);
|
||||
|
||||
playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -373,7 +411,6 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
|||
break;
|
||||
|
||||
case PlayerControlEvent.SetCaptions:
|
||||
console.log(player.isCaptionsEnabled());
|
||||
if (player.isCaptionsEnabled()) {
|
||||
playerCtrlCaptions.setAttribute("class", "captions_on");
|
||||
videoCaptions.setAttribute("style", "display: block");
|
||||
|
@ -453,18 +490,42 @@ PlayerCtrlProgressBarInteractiveArea.onmouseenter = (e: MouseEvent) => {
|
|||
if (e.buttons === 0) {
|
||||
volumeChanging = false;
|
||||
}
|
||||
|
||||
scrubbingMouseUIHandler(e);
|
||||
};
|
||||
PlayerCtrlProgressBarInteractiveArea.onmouseleave = () => { playerCtrlProgressBarPosition.setAttribute("style", "display: none"); };
|
||||
PlayerCtrlProgressBarInteractiveArea.onmousemove = (e: MouseEvent) => { scrubbingMouseHandler(e) };
|
||||
|
||||
function scrubbingMouseHandler(e: MouseEvent) {
|
||||
if (scrubbing && e.buttons === 1) {
|
||||
const progressBarOffset = e.offsetX - 8;
|
||||
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
|
||||
const time = Math.round((progressBarOffset / progressBarWidth) * player.getDuration());
|
||||
let time = Math.round((progressBarOffset / progressBarWidth) * player.getDuration());
|
||||
time = Math.min(player.getDuration(), Math.max(0.0, time));
|
||||
|
||||
playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate);
|
||||
if (scrubbing && e.buttons === 1) {
|
||||
player.setCurrentTime(time);
|
||||
}
|
||||
|
||||
scrubbingMouseUIHandler(e);
|
||||
}
|
||||
|
||||
function scrubbingMouseUIHandler(e: MouseEvent) {
|
||||
const progressBarOffset = e.offsetX - 8;
|
||||
const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
|
||||
let time = isLive ? Math.round((1 - (progressBarOffset / progressBarWidth)) * player.getDuration()) : Math.round((progressBarOffset / progressBarWidth) * player.getDuration());
|
||||
time = Math.min(player.getDuration(), Math.max(0.0, time));
|
||||
|
||||
if (scrubbing && isLive && e.buttons === 1) {
|
||||
isLivePosition = false;
|
||||
playerCtrlLiveBadge.setAttribute("style", `background-color: #595959`);
|
||||
}
|
||||
|
||||
const livePrefix = isLive && Math.floor(time) !== 0 ? "-" : "";
|
||||
playerCtrlProgressBarPosition.textContent = isLive ? `${livePrefix}${formatDuration(time)}` : formatDuration(time);
|
||||
|
||||
let offset = e.offsetX - (playerCtrlProgressBarPosition.offsetWidth / 2);
|
||||
offset = Math.min(PlayerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBarPosition.offsetWidth / 1), Math.max(8, offset));
|
||||
playerCtrlProgressBarPosition.setAttribute("style", `display: block; left: ${offset}px`);
|
||||
}
|
||||
|
||||
playerCtrlVolumeBarInteractiveArea.onmousedown = (e: MouseEvent) => { volumeChanging = true; volumeChangeMouseHandler(e) };
|
||||
|
@ -505,6 +566,15 @@ function volumeChangeHandler(volume: number) {
|
|||
player.setVolume(volume);
|
||||
}
|
||||
|
||||
playerCtrlLiveBadge.onclick = () => {
|
||||
if (!isLivePosition) {
|
||||
isLivePosition = true;
|
||||
|
||||
player.setCurrentTime(player.getDuration() - livePositionDelta);
|
||||
playerCtrlLiveBadge.setAttribute("style", `background-color: red`);
|
||||
}
|
||||
};
|
||||
|
||||
playerCtrlCaptions.onclick = () => { player.enableCaptions(!player.isCaptionsEnabled()); playerCtrlStateUpdate(PlayerControlEvent.SetCaptions); };
|
||||
playerCtrlSpeed.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleSpeedMenu); };
|
||||
playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<div id="progressBar" ref="progressBar" class="progressBar" ></div>
|
||||
<div id="progressBarBuffer" class="progressBarBuffer" ></div>
|
||||
<div id="progressBarProgress" class="progressBarProgress" ></div>
|
||||
<div id="progressBarPosition" class="progressBarPosition" ></div>
|
||||
<!-- <div class="progressBarChapterContainer"></div> -->
|
||||
<div id="progressBarHandle" class="progressBarHandle" ></div>
|
||||
<div id="progressBarInteractiveArea" class="progressBarInteractiveArea" ></div>
|
||||
|
|
|
@ -183,6 +183,20 @@ body {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.progressBarPosition {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
padding: 2px 5px;
|
||||
|
||||
font-family: InterVariable;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
border-radius: 3px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.progressBarHandle {
|
||||
position: absolute;
|
||||
/* bottom: 70px; */
|
||||
|
@ -227,10 +241,13 @@ body {
|
|||
|
||||
.liveBadge {
|
||||
background-color: red;
|
||||
margin-top: -2px;
|
||||
padding: 5px 5px;
|
||||
/* margin-top: -2px; */
|
||||
/* padding: 5px 5px; */
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
/* margin-left: 10px; */
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue