Use explicit YouTube play pause actions

This commit is contained in:
codex
2026-06-10 19:54:37 +02:00
parent 3e6a94ae2e
commit 5f86df62cf
3 changed files with 66 additions and 19 deletions
+8 -2
View File
@@ -316,6 +316,7 @@ When KEEP-OPEN is nil, close PROCESS after writing."
(from (alist-get 'from payload))
(type (alist-get 'type payload))
(video-id (format "%s" (or (alist-get 'videoId payload) "")))
(action (format "%s" (or (alist-get 'action payload) "")))
(playlist (kkkmeet-server--normalize-playlist (alist-get 'playlist payload)))
(safe-room-id (if (string-empty-p room-id)
kkkmeet-server--default-room-id
@@ -324,9 +325,13 @@ When KEEP-OPEN is nil, close PROCESS after writing."
(let* ((active-video-id (if (seq-some
(lambda (item)
(equal video-id (alist-get 'videoId item)))
(append playlist nil))
(append playlist nil))
video-id
""))
(safe-action (if (and (equal action "play")
(not (string-empty-p active-video-id)))
"play"
"pause"))
(watch-state `((type . "watch-state")
(from . ,from)
(name . ,(kkkmeet-server--normalize-name
@@ -334,7 +339,8 @@ When KEEP-OPEN is nil, close PROCESS after writing."
(videoId . ,active-video-id)
(playlist . ,playlist)
(position . ,(or (alist-get 'position payload) 0))
(playing . ,(if (alist-get 'playing payload) t :false))
(playing . ,(if (equal safe-action "play") t :false))
(action . ,safe-action)
(updatedAt . ,(or (alist-get 'updatedAt payload)
(floor (* 1000 (float-time))))))))
(puthash safe-room-id watch-state kkkmeet-server--watch-states)
+53 -15
View File
@@ -44,6 +44,8 @@ let youtubePlayer;
let youtubeReady = false;
let currentYoutubeVideoId = "";
let applyingWatchStateUntil = 0;
let pauseEnforcementUntil = 0;
let desiredYoutubePlaying = false;
let watchPlaylist = [];
const peers = new Map();
@@ -297,9 +299,15 @@ async function ensureYoutubePlayer() {
return youtubePlayer;
}
async function sendWatchState({ videoId = currentYoutubeVideoId, position = 0, playing = false }) {
async function sendWatchState({
videoId = currentYoutubeVideoId,
position = 0,
playing = false,
action = playing ? "play" : "pause"
}) {
if (!peerId) return;
const watchAction = action === "play" ? "play" : "pause";
const playlist = playlistWithActivePosition(position);
await fetch(`/watch/${encodeURIComponent(roomId)}`, {
@@ -312,7 +320,8 @@ async function sendWatchState({ videoId = currentYoutubeVideoId, position = 0, p
videoId,
playlist,
position,
playing,
playing: watchAction === "play" && Boolean(videoId),
action: watchAction,
updatedAt: Date.now()
})
});
@@ -326,7 +335,8 @@ function currentWatchState(playing) {
videoId: currentYoutubeVideoId,
playlist: watchPlaylist,
position: snapshot.position,
playing
playing,
action: playing ? "play" : "pause"
};
}
@@ -336,11 +346,25 @@ function handleYoutubeStateChange(event) {
return;
}
const state = currentWatchState(event.data === YT.PlayerState.PLAYING);
if (
event.data === YT.PlayerState.PLAYING &&
!desiredYoutubePlaying &&
Date.now() < pauseEnforcementUntil
) {
youtubePlayer?.pauseVideo?.();
return;
}
const action = event.data === YT.PlayerState.PLAYING ? "play" : "pause";
desiredYoutubePlaying = action === "play";
const state = currentWatchState(action === "play");
if (state) {
state.action = action;
if (event.data === YT.PlayerState.ENDED) {
state.position = 0;
state.playing = false;
state.action = "pause";
desiredYoutubePlaying = false;
watchPlaylist = playlistWithActivePosition(0);
state.playlist = watchPlaylist;
renderPlaylist();
@@ -352,9 +376,11 @@ function handleYoutubeStateChange(event) {
}
}
async function applyWatchState(state) {
async function applyWatchState(state, { suppressEvents = true } = {}) {
const shouldPlay = state?.action === "play" || (!state?.action && Boolean(state?.playing));
watchPlaylist = normalizedPlaylist(state?.playlist);
currentYoutubeVideoId = state?.videoId || "";
desiredYoutubePlaying = shouldPlay;
if (currentYoutubeVideoId) {
const position = Math.max(0, Number(state.position) || 0);
watchPlaylist = watchPlaylist.map((item) => (
@@ -364,25 +390,31 @@ async function applyWatchState(state) {
renderPlaylist();
if (!currentYoutubeVideoId) {
desiredYoutubePlaying = false;
youtubePlayer?.stopVideo?.();
youtubeShell.classList.remove("is-active");
setWatchCollapsed(true);
return;
}
if (state.playing) {
if (shouldPlay) {
setWatchCollapsed(false);
}
youtubeShell.classList.add("is-active");
const player = await ensureYoutubePlayer();
applyingWatchStateUntil = Date.now() + 1200;
if (suppressEvents) {
applyingWatchStateUntil = Date.now() + 3000;
}
if (!shouldPlay) {
pauseEnforcementUntil = Math.max(pauseEnforcementUntil, Date.now() + 3000);
}
const elapsed = state.playing ? Math.max(0, (Date.now() - state.updatedAt) / 1000) : 0;
const elapsed = shouldPlay ? Math.max(0, (Date.now() - state.updatedAt) / 1000) : 0;
const position = Math.max(0, Number(state.position || 0) + elapsed);
if (youtubeReady) {
if (state.playing) {
if (shouldPlay) {
const sameVideo = player.getVideoData?.().video_id === currentYoutubeVideoId;
const playerState = player.getPlayerState?.();
if (sameVideo && playerState === YT.PlayerState.PLAYING) {
@@ -405,12 +437,18 @@ async function applyWatchState(state) {
setTimeout(() => {
player.pauseVideo();
}, 150);
setTimeout(() => {
if (!desiredYoutubePlaying) player.pauseVideo();
}, 1200);
setTimeout(() => {
if (!desiredYoutubePlaying) player.pauseVideo();
}, 2500);
}
} else {
const interval = setInterval(() => {
if (!youtubeReady) return;
clearInterval(interval);
applyWatchState(state).catch(console.error);
applyWatchState(state, { suppressEvents }).catch(console.error);
}, 100);
}
}
@@ -803,7 +841,7 @@ async function addYoutubeVideo(value) {
position: savedPositionForVideo(videoId),
playing: true,
updatedAt: Date.now()
});
}, { suppressEvents: false });
await sendWatchState({ videoId, position: savedPositionForVideo(videoId), playing: true });
}
@@ -818,7 +856,7 @@ async function switchYoutubeVideo(videoId) {
position,
playing: true,
updatedAt: Date.now()
});
}, { suppressEvents: false });
await sendWatchState({ videoId, position, playing: true });
}
@@ -835,7 +873,7 @@ async function deleteYoutubeVideo(videoId) {
position,
playing: Boolean(nextVideoId),
updatedAt: Date.now()
});
}, { suppressEvents: false });
await sendWatchState({ videoId: nextVideoId, position, playing: Boolean(nextVideoId) });
}
@@ -847,8 +885,8 @@ async function clearYoutubePlaylist() {
position: 0,
playing: false,
updatedAt: Date.now()
});
await sendWatchState({ videoId: "", position: 0, playing: false });
}, { suppressEvents: false });
await sendWatchState({ videoId: "", position: 0, playing: false, action: "pause" });
}
async function startScreenShare() {
+5 -2
View File
@@ -221,14 +221,17 @@ const server = http.createServer(async (req, res) => {
const playlist = normalizePlaylist(payload.playlist);
const videoId = String(payload.videoId || "").trim();
const activeVideoId = playlist.some((item) => item.videoId === videoId) ? videoId : "";
const action = payload.action === "play" && activeVideoId ? "play" : "pause";
const watchState = {
type: "watch-state",
from: payload.from,
name: normalizeName(payload.name),
videoId: playlist.some((item) => item.videoId === videoId) ? videoId : "",
videoId: activeVideoId,
playlist,
position: Number(payload.position) || 0,
playing: Boolean(payload.playing),
playing: action === "play",
action,
updatedAt: Number(payload.updatedAt) || Date.now()
};