mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
unmute me
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -78,7 +78,7 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DM5rqxP4.js"></script>
|
<script type="module" crossorigin src="/assets/index-C_Ron8xr.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DwpUkSbG.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DwpUkSbG.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { isFeatureEnabled } from '../../lib/features.js';
|
|||||||
import { innerFlowClass } from './constants.js';
|
import { innerFlowClass } from './constants.js';
|
||||||
|
|
||||||
const PTZ_CAMERA_ID = 'ptz-camera';
|
const PTZ_CAMERA_ID = 'ptz-camera';
|
||||||
|
const PTZ_AUDIO_RETRY_MS = 1000;
|
||||||
|
|
||||||
function formatRemaining(deadline) {
|
function formatRemaining(deadline) {
|
||||||
const remaining = Math.max(0, Math.ceil((Number(deadline || 0) - Date.now()) / 1000));
|
const remaining = Math.max(0, Math.ceil((Number(deadline || 0) - Date.now()) / 1000));
|
||||||
@@ -81,6 +82,7 @@ function PtzLiveVideo({ enabled }) {
|
|||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const playerRef = useRef(null);
|
const playerRef = useRef(null);
|
||||||
const retryTimerRef = useRef(null);
|
const retryTimerRef = useRef(null);
|
||||||
|
const playTimerRef = useRef(null);
|
||||||
const [status, setStatus] = useState('idle');
|
const [status, setStatus] = useState('idle');
|
||||||
const [retryVersion, setRetryVersion] = useState(0);
|
const [retryVersion, setRetryVersion] = useState(0);
|
||||||
const sources = useVideoRequests(
|
const sources = useVideoRequests(
|
||||||
@@ -114,6 +116,7 @@ function PtzLiveVideo({ enabled }) {
|
|||||||
url: source.url,
|
url: source.url,
|
||||||
token: source.token,
|
token: source.token,
|
||||||
video: videoRef.current,
|
video: videoRef.current,
|
||||||
|
startMuted: false,
|
||||||
onStatus: (nextStatus) => {
|
onStatus: (nextStatus) => {
|
||||||
setStatus(nextStatus);
|
setStatus(nextStatus);
|
||||||
if (['error', 'failed', 'disconnected', 'closed'].includes(String(nextStatus || '').toLowerCase())) {
|
if (['error', 'failed', 'disconnected', 'closed'].includes(String(nextStatus || '').toLowerCase())) {
|
||||||
@@ -132,12 +135,45 @@ function PtzLiveVideo({ enabled }) {
|
|||||||
};
|
};
|
||||||
}, [enabled, scheduleRetry, source?.token, source?.url]);
|
}, [enabled, scheduleRetry, source?.token, source?.url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const video = videoRef.current;
|
||||||
|
if (!enabled || !source?.url || !video) {
|
||||||
|
if (playTimerRef.current) clearInterval(playTimerRef.current);
|
||||||
|
playTimerRef.current = null;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
The server now publishes inline Opus audio on the PTZ WHEP stream. The
|
||||||
|
shared WHEP helper starts playback once, but browsers can still reject or
|
||||||
|
pause audible media depending on the exact timing of the fullscreen/user
|
||||||
|
gesture. Retry the same element with muted=false so unmuting is not a
|
||||||
|
manual DevTools-only operation.
|
||||||
|
*/
|
||||||
|
const attemptPlay = () => {
|
||||||
|
const target = videoRef.current;
|
||||||
|
if (!target) return;
|
||||||
|
target.muted = false;
|
||||||
|
if (!target.paused && !target.ended) return;
|
||||||
|
target.play().catch(() => {});
|
||||||
|
};
|
||||||
|
attemptPlay();
|
||||||
|
playTimerRef.current = setInterval(attemptPlay, PTZ_AUDIO_RETRY_MS);
|
||||||
|
return () => {
|
||||||
|
if (playTimerRef.current) clearInterval(playTimerRef.current);
|
||||||
|
playTimerRef.current = null;
|
||||||
|
};
|
||||||
|
}, [enabled, source?.url, status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (retryTimerRef.current) {
|
if (retryTimerRef.current) {
|
||||||
clearTimeout(retryTimerRef.current);
|
clearTimeout(retryTimerRef.current);
|
||||||
retryTimerRef.current = null;
|
retryTimerRef.current = null;
|
||||||
}
|
}
|
||||||
|
if (playTimerRef.current) {
|
||||||
|
clearInterval(playTimerRef.current);
|
||||||
|
playTimerRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,19 @@ function buildAuthHeader(token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class WhepPlayer {
|
export class WhepPlayer {
|
||||||
constructor({ url, token, video, onStatus, audioOnly = false, receiveAudio = true }) {
|
constructor({ url, token, video, onStatus, audioOnly = false, receiveAudio = true, startMuted = null }) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.video = video;
|
this.video = video;
|
||||||
this.audioOnly = audioOnly;
|
this.audioOnly = audioOnly;
|
||||||
this.receiveAudio = receiveAudio;
|
this.receiveAudio = receiveAudio;
|
||||||
|
/*
|
||||||
|
Browser autoplay usually requires normal video streams to start muted,
|
||||||
|
while audio-only streams need to start audible. Keep that historical
|
||||||
|
default, but allow a caller that is already behind a user gesture, such as
|
||||||
|
the PTZ fullscreen controller, to request audible inline audio.
|
||||||
|
*/
|
||||||
|
this.startMuted = startMuted === null ? !audioOnly : Boolean(startMuted);
|
||||||
this.pc = null;
|
this.pc = null;
|
||||||
this.abortController = null;
|
this.abortController = null;
|
||||||
this.onStatus = onStatus;
|
this.onStatus = onStatus;
|
||||||
@@ -50,7 +57,7 @@ export class WhepPlayer {
|
|||||||
if (!this.video) return;
|
if (!this.video) return;
|
||||||
this.video.playsInline = true;
|
this.video.playsInline = true;
|
||||||
this.video.autoplay = true;
|
this.video.autoplay = true;
|
||||||
this.video.muted = this.audioOnly ? false : true;
|
this.video.muted = this.startMuted;
|
||||||
if (typeof this.video.disableRemotePlayback !== 'undefined') {
|
if (typeof this.video.disableRemotePlayback !== 'undefined') {
|
||||||
this.video.disableRemotePlayback = true;
|
this.video.disableRemotePlayback = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user