826 lines
24 KiB
JavaScript
826 lines
24 KiB
JavaScript
var registry = window.pulsePlayerRegionTypes;
|
|
|
|
function renderRtmpRegion(region, regionContent) {
|
|
var url = String(regionContent.value || '').trim();
|
|
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
|
|
var skipUnavailable = Boolean(currentPlaylistSkipUnavailableRtmp);
|
|
if (!url) {
|
|
return '';
|
|
}
|
|
|
|
return '<div class="template-region rtmp" style="' + region.baseStyle + '"><video class="template-region-rtmp-video" data-rtmp-source="' + escapeHtml(url) + '" data-rtmp-disable-audio="' + (disableAudio ? '1' : '0') + '" data-rtmp-skip-unavailable="' + (skipUnavailable ? '1' : '0') + '" autoplay playsinline preload="auto" tabindex="-1" disablepictureinpicture' + (disableAudio ? ' muted' : '') + '></video><div class="template-region-placeholder template-region-rtmp-placeholder">Loading RTMP stream...</div></div>';
|
|
}
|
|
|
|
function getRtmpSessionUrl(sourceUrl, disableAudio) {
|
|
return '/api/rtmp/session?source=' + encodeURIComponent(sourceUrl) + '&disableAudio=' + (disableAudio ? '1' : '0');
|
|
}
|
|
|
|
var rtmpAvailabilityCache = Object.create(null);
|
|
var rtmpAvailabilityRetryMs = 5000;
|
|
var rtmpWarmupTimers = Object.create(null);
|
|
var rtmpBrowserReadyCache = Object.create(null);
|
|
var rtmpBrowserWarmupTimers = Object.create(null);
|
|
var rtmpBrowserWarmupContainer = null;
|
|
var rtmpBrowserWarmupStates = Object.create(null);
|
|
|
|
function getRtmpAvailabilityKey(sourceUrl, disableAudio) {
|
|
return String(sourceUrl || '').trim() + '\n' + (disableAudio ? '1' : '0');
|
|
}
|
|
|
|
function setRtmpAvailability(sourceUrl, disableAudio, available) {
|
|
var key = getRtmpAvailabilityKey(sourceUrl, disableAudio);
|
|
rtmpAvailabilityCache[key] = {
|
|
available: available === null ? null : Boolean(available),
|
|
pending: available === null,
|
|
checkedAt: Date.now()
|
|
};
|
|
}
|
|
|
|
function getRtmpAvailability(sourceUrl, disableAudio) {
|
|
return rtmpAvailabilityCache[getRtmpAvailabilityKey(sourceUrl, disableAudio)] || null;
|
|
}
|
|
|
|
function isRtmpAvailabilityStale(status) {
|
|
if (!status || !status.checkedAt) {
|
|
return true;
|
|
}
|
|
return Date.now() - Number(status.checkedAt || 0) >= rtmpAvailabilityRetryMs;
|
|
}
|
|
|
|
function isRtmpSlideUnavailable(slide) {
|
|
if (!slide || !slide.template || !Array.isArray(slide.template.regions)) {
|
|
return false;
|
|
}
|
|
|
|
var content = slide.content || {};
|
|
return slide.template.regions.some(function (region) {
|
|
if (region.region_type !== 'rtmp') {
|
|
return false;
|
|
}
|
|
|
|
var regionContent = content[region.region_key] || {};
|
|
var sourceUrl = String(regionContent.value || '').trim();
|
|
if (!sourceUrl) {
|
|
return false;
|
|
}
|
|
|
|
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
|
|
var serverStatus = getRtmpAvailability(sourceUrl, disableAudio);
|
|
return Boolean(!serverStatus || isRtmpAvailabilityStale(serverStatus) || serverStatus.available !== true);
|
|
});
|
|
}
|
|
|
|
function clearRtmpAvailabilityForSlide(slide) {
|
|
if (!slide || !slide.template || !Array.isArray(slide.template.regions)) {
|
|
return;
|
|
}
|
|
|
|
var content = slide.content || {};
|
|
slide.template.regions.forEach(function (region) {
|
|
if (region.region_type !== 'rtmp') {
|
|
return;
|
|
}
|
|
|
|
var regionContent = content[region.region_key] || {};
|
|
var sourceUrl = String(regionContent.value || '').trim();
|
|
if (!sourceUrl) {
|
|
return;
|
|
}
|
|
|
|
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
|
|
setRtmpAvailability(sourceUrl, disableAudio, null);
|
|
});
|
|
}
|
|
|
|
function setRtmpBrowserReady(sourceUrl, disableAudio, ready) {
|
|
var key = getRtmpAvailabilityKey(sourceUrl, disableAudio);
|
|
rtmpBrowserReadyCache[key] = {
|
|
ready: ready === null ? null : Boolean(ready),
|
|
checkedAt: Date.now()
|
|
};
|
|
}
|
|
|
|
function getRtmpBrowserReady(sourceUrl, disableAudio) {
|
|
return rtmpBrowserReadyCache[getRtmpAvailabilityKey(sourceUrl, disableAudio)] || null;
|
|
}
|
|
|
|
function isRtmpBrowserReadyStale(status) {
|
|
if (!status || !status.checkedAt) {
|
|
return true;
|
|
}
|
|
return Date.now() - Number(status.checkedAt || 0) >= rtmpAvailabilityRetryMs;
|
|
}
|
|
|
|
function ensureRtmpBrowserWarmupContainer() {
|
|
if (rtmpBrowserWarmupContainer) {
|
|
return rtmpBrowserWarmupContainer;
|
|
}
|
|
|
|
rtmpBrowserWarmupContainer = document.createElement('div');
|
|
rtmpBrowserWarmupContainer.className = 'rtmp-browser-warmups';
|
|
rtmpBrowserWarmupContainer.setAttribute('aria-hidden', 'true');
|
|
document.body.appendChild(rtmpBrowserWarmupContainer);
|
|
return rtmpBrowserWarmupContainer;
|
|
}
|
|
|
|
function getRtmpBrowserWarmupState(sourceUrl, disableAudio) {
|
|
return rtmpBrowserWarmupStates[getRtmpAvailabilityKey(sourceUrl, disableAudio)] || null;
|
|
}
|
|
|
|
function clearRtmpBrowserWarmupState(sourceUrl, disableAudio) {
|
|
var key = getRtmpAvailabilityKey(sourceUrl, disableAudio);
|
|
var state = rtmpBrowserWarmupStates[key];
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
if (state.video && state.video.__rtmpBrowserWarmupTimer) {
|
|
window.clearTimeout(state.video.__rtmpBrowserWarmupTimer);
|
|
state.video.__rtmpBrowserWarmupTimer = null;
|
|
}
|
|
if (state.video && state.video.__rtmpHls) {
|
|
try {
|
|
state.video.__rtmpHls.destroy();
|
|
} catch (_error) {
|
|
// ignore cleanup errors
|
|
}
|
|
state.video.__rtmpHls = null;
|
|
}
|
|
if (state.wrapper && state.wrapper.parentNode) {
|
|
state.wrapper.parentNode.removeChild(state.wrapper);
|
|
}
|
|
delete rtmpBrowserWarmupStates[key];
|
|
}
|
|
|
|
function warmupRtmpBrowser(sourceUrl, disableAudio) {
|
|
var key = getRtmpAvailabilityKey(sourceUrl, disableAudio);
|
|
var existing = rtmpBrowserWarmupStates[key];
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
var wrapper = document.createElement('div');
|
|
wrapper.className = 'rtmp-browser-warmup-entry';
|
|
wrapper.setAttribute('aria-hidden', 'true');
|
|
|
|
var video = document.createElement('video');
|
|
video.className = 'template-region-rtmp-video';
|
|
video.dataset.rtmpSource = sourceUrl;
|
|
video.dataset.rtmpDisableAudio = disableAudio ? '1' : '0';
|
|
video.autoplay = true;
|
|
video.playsInline = true;
|
|
video.preload = 'auto';
|
|
video.tabIndex = -1;
|
|
video.disablePictureInPicture = true;
|
|
video.muted = disableAudio;
|
|
|
|
wrapper.appendChild(video);
|
|
ensureRtmpBrowserWarmupContainer().appendChild(wrapper);
|
|
|
|
var state = {
|
|
wrapper: wrapper,
|
|
video: video,
|
|
sourceUrl: sourceUrl,
|
|
disableAudio: disableAudio
|
|
};
|
|
rtmpBrowserWarmupStates[key] = state;
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, null);
|
|
|
|
var markReady = function () {
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, true);
|
|
clearRtmpBrowserWarmupState(sourceUrl, disableAudio);
|
|
};
|
|
|
|
var markNotReady = function () {
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, false);
|
|
};
|
|
|
|
video.addEventListener('canplay', markReady, { once: true });
|
|
video.addEventListener('playing', markReady, { once: true });
|
|
video.addEventListener('error', markNotReady, { once: true });
|
|
|
|
if (window.Hls && window.Hls.isSupported && window.Hls.isSupported()) {
|
|
var hls = new window.Hls({
|
|
enableWorker: true,
|
|
lowLatencyMode: true,
|
|
liveSyncDurationCount: 4,
|
|
liveMaxLatencyDurationCount: 8,
|
|
maxBufferLength: 10,
|
|
maxLiveSyncPlaybackRate: 1,
|
|
backBufferLength: 10
|
|
});
|
|
video.__rtmpHls = hls;
|
|
hls.attachMedia(video);
|
|
hls.on(window.Hls.Events.MEDIA_ATTACHED, function () {
|
|
requestWarmupPlaylist(sourceUrl, disableAudio, video, markNotReady);
|
|
});
|
|
hls.on(window.Hls.Events.ERROR, function (_event, data) {
|
|
if (data && data.fatal) {
|
|
markNotReady();
|
|
clearRtmpBrowserWarmupState(sourceUrl, disableAudio);
|
|
}
|
|
});
|
|
return state;
|
|
}
|
|
|
|
if (video.canPlayType && video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
requestWarmupPlaylist(sourceUrl, disableAudio, video, markNotReady);
|
|
return state;
|
|
}
|
|
|
|
markNotReady();
|
|
return state;
|
|
}
|
|
|
|
function requestWarmupPlaylist(sourceUrl, disableAudio, video, onFailure) {
|
|
fetch(getRtmpSessionUrl(sourceUrl, disableAudio), {
|
|
credentials: 'same-origin'
|
|
}).then(function (response) {
|
|
return response.json().catch(function () {
|
|
return null;
|
|
}).then(function (payload) {
|
|
return {
|
|
response: response,
|
|
payload: payload
|
|
};
|
|
});
|
|
}).then(function (result) {
|
|
if (!result || !result.response) {
|
|
throw new Error('Unable to initialize RTMP stream.');
|
|
}
|
|
|
|
var response = result.response;
|
|
var payload = result.payload || null;
|
|
var playlistUrl = payload && payload.playlistUrl ? String(payload.playlistUrl).trim() : '';
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 503 && payload && payload.probing) {
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, null);
|
|
if (video && !video.__rtmpBrowserWarmupTimer) {
|
|
video.__rtmpBrowserWarmupTimer = window.setTimeout(function () {
|
|
video.__rtmpBrowserWarmupTimer = null;
|
|
requestWarmupPlaylist(sourceUrl, disableAudio, video, onFailure);
|
|
}, 500);
|
|
}
|
|
return;
|
|
}
|
|
throw new Error('Unable to initialize RTMP stream.');
|
|
}
|
|
|
|
if (!playlistUrl) {
|
|
throw new Error('RTMP playlist URL was not returned.');
|
|
}
|
|
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, false);
|
|
|
|
if (video.__rtmpHls && window.Hls && window.Hls.isSupported && window.Hls.isSupported()) {
|
|
video.__rtmpHls.loadSource(playlistUrl);
|
|
return;
|
|
}
|
|
|
|
if (video) {
|
|
video.src = playlistUrl;
|
|
video.play().catch(function () {
|
|
return null;
|
|
});
|
|
}
|
|
}).catch(function () {
|
|
setRtmpBrowserReady(sourceUrl, disableAudio, false);
|
|
if (typeof onFailure === 'function') {
|
|
onFailure();
|
|
}
|
|
});
|
|
}
|
|
|
|
function getRtmpWarmupSlides(sourceSlides, targetIndex) {
|
|
var availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
|
|
if (!availableSlides.length) {
|
|
return [];
|
|
}
|
|
|
|
var normalizedIndex = Number(targetIndex || 0);
|
|
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
|
|
normalizedIndex = 0;
|
|
}
|
|
|
|
var warmupSlides = [];
|
|
var nextSlide = availableSlides[normalizedIndex + 1];
|
|
|
|
if (nextSlide) {
|
|
warmupSlides.push(nextSlide);
|
|
}
|
|
|
|
return warmupSlides;
|
|
}
|
|
|
|
function getRtmpWarmupEntries(sourceSlides) {
|
|
var entries = [];
|
|
var seen = Object.create(null);
|
|
|
|
(Array.isArray(sourceSlides) ? sourceSlides : []).forEach(function (slide) {
|
|
var content = slide && slide.content ? slide.content : {};
|
|
var regions = slide && slide.template && Array.isArray(slide.template.regions) ? slide.template.regions : [];
|
|
regions.forEach(function (region) {
|
|
if (region.region_type !== 'rtmp') {
|
|
return;
|
|
}
|
|
|
|
var regionContent = content[region.region_key] || {};
|
|
var url = String(regionContent.value || '').trim();
|
|
if (!url) {
|
|
return;
|
|
}
|
|
|
|
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
|
|
var key = getRtmpAvailabilityKey(url, disableAudio);
|
|
if (seen[key]) {
|
|
return;
|
|
}
|
|
|
|
seen[key] = true;
|
|
entries.push({
|
|
url: url,
|
|
disableAudio: disableAudio,
|
|
key: key
|
|
});
|
|
});
|
|
});
|
|
|
|
return entries;
|
|
}
|
|
|
|
function syncRtmpWarmups(sourceSlides, targetIndex) {
|
|
var entries = getRtmpWarmupEntries(getRtmpWarmupSlides(sourceSlides, targetIndex));
|
|
|
|
entries.forEach(function (entry) {
|
|
var status = getRtmpAvailability(entry.url, entry.disableAudio);
|
|
if (status && !isRtmpAvailabilityStale(status) && status.available === true) {
|
|
return;
|
|
}
|
|
|
|
if (rtmpWarmupTimers[entry.key]) {
|
|
return;
|
|
}
|
|
|
|
function requestWarmup() {
|
|
fetch(getRtmpSessionUrl(entry.url, entry.disableAudio), {
|
|
credentials: 'same-origin'
|
|
}).then(function (response) {
|
|
return response.json().catch(function () {
|
|
return null;
|
|
}).then(function (payload) {
|
|
return {
|
|
response: response,
|
|
payload: payload
|
|
};
|
|
});
|
|
}).then(function (result) {
|
|
rtmpWarmupTimers[entry.key] = null;
|
|
if (!result || !result.response) {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
return;
|
|
}
|
|
|
|
if (!result.response.ok) {
|
|
if (result.response.status === 503 && result.payload && result.payload.probing) {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, null);
|
|
rtmpWarmupTimers[entry.key] = window.setTimeout(function () {
|
|
rtmpWarmupTimers[entry.key] = null;
|
|
requestWarmup();
|
|
}, 500);
|
|
return;
|
|
}
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
return;
|
|
}
|
|
|
|
setRtmpAvailability(entry.url, entry.disableAudio, true);
|
|
}).catch(function () {
|
|
rtmpWarmupTimers[entry.key] = null;
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
});
|
|
}
|
|
|
|
setRtmpAvailability(entry.url, entry.disableAudio, null);
|
|
requestWarmup();
|
|
});
|
|
}
|
|
|
|
async function probeRtmpSlideAvailability(slide) {
|
|
var entries = getRtmpWarmupEntries([slide]);
|
|
if (!entries.length) {
|
|
return true;
|
|
}
|
|
|
|
var results = await Promise.all(entries.map(function (entry) {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, null);
|
|
return fetch(getRtmpSessionUrl(entry.url, entry.disableAudio), {
|
|
credentials: 'same-origin'
|
|
}).then(function (response) {
|
|
return response.json().catch(function () {
|
|
return null;
|
|
}).then(function (payload) {
|
|
return {
|
|
response: response,
|
|
payload: payload
|
|
};
|
|
});
|
|
}).then(function (result) {
|
|
if (!result || !result.response) {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
return false;
|
|
}
|
|
|
|
if (!result.response.ok) {
|
|
if (result.response.status === 503 && result.payload && result.payload.probing) {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, null);
|
|
return false;
|
|
}
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
return false;
|
|
}
|
|
|
|
setRtmpAvailability(entry.url, entry.disableAudio, true);
|
|
return true;
|
|
}).catch(function () {
|
|
setRtmpAvailability(entry.url, entry.disableAudio, false);
|
|
return false;
|
|
});
|
|
}));
|
|
|
|
return results.every(function (value) {
|
|
return Boolean(value);
|
|
});
|
|
}
|
|
|
|
function syncRtmpBrowserWarmups(sourceSlides, targetIndex) {
|
|
return;
|
|
}
|
|
|
|
function isRtmpBrowserUnavailable(slide) {
|
|
if (!slide || !slide.template || !Array.isArray(slide.template.regions)) {
|
|
return false;
|
|
}
|
|
|
|
var content = slide.content || {};
|
|
return slide.template.regions.some(function (region) {
|
|
if (region.region_type !== 'rtmp') {
|
|
return false;
|
|
}
|
|
|
|
var regionContent = content[region.region_key] || {};
|
|
var sourceUrl = String(regionContent.value || '').trim();
|
|
if (!sourceUrl) {
|
|
return false;
|
|
}
|
|
|
|
var disableAudio = regionContent.disable_audio === undefined ? true : Boolean(regionContent.disable_audio);
|
|
var status = getRtmpBrowserReady(sourceUrl, disableAudio);
|
|
return Boolean(!status || isRtmpBrowserReadyStale(status) || status.ready !== true);
|
|
});
|
|
}
|
|
|
|
function bindRtmpVideoPlaceholder(video, placeholder) {
|
|
if (!video) {
|
|
return;
|
|
}
|
|
|
|
var markReady = function () {
|
|
if (placeholder) {
|
|
placeholder.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
video.addEventListener('canplay', markReady, { once: true });
|
|
video.addEventListener('playing', markReady, { once: true });
|
|
if (video.readyState >= 2) {
|
|
markReady();
|
|
}
|
|
}
|
|
|
|
function startRtmpPlayback(video, sourceUrl, disableAudio, skipUnavailable, placeholder, isPreload, onFailure) {
|
|
var startupTimeoutMs = skipUnavailable ? 6000 : 0;
|
|
var startupTimer = null;
|
|
var probeRetryTimer = null;
|
|
var webReceiveTimer = null;
|
|
var webReceiveRetryUsed = false;
|
|
|
|
function failPlayback(message, silent) {
|
|
if (video.dataset.rtmpFailureHandled === '1') {
|
|
return;
|
|
}
|
|
video.dataset.rtmpFailureHandled = '1';
|
|
if (typeof onFailure === 'function') {
|
|
onFailure(message, silent);
|
|
return;
|
|
}
|
|
if (!isPreload && skipUnavailable && typeof handleRtmpPlaybackFailure === 'function' && handleRtmpPlaybackFailure(message, { silent: Boolean(silent) })) {
|
|
return;
|
|
}
|
|
if (placeholder) {
|
|
placeholder.style.display = 'flex';
|
|
placeholder.textContent = message;
|
|
}
|
|
}
|
|
|
|
function clearStartupTimer() {
|
|
if (startupTimer) {
|
|
window.clearTimeout(startupTimer);
|
|
startupTimer = null;
|
|
}
|
|
if (video.__rtmpStartupTimer) {
|
|
video.__rtmpStartupTimer = null;
|
|
}
|
|
if (probeRetryTimer) {
|
|
window.clearTimeout(probeRetryTimer);
|
|
probeRetryTimer = null;
|
|
}
|
|
if (video.__rtmpProbeRetryTimer) {
|
|
video.__rtmpProbeRetryTimer = null;
|
|
}
|
|
if (webReceiveTimer) {
|
|
window.clearTimeout(webReceiveTimer);
|
|
webReceiveTimer = null;
|
|
}
|
|
if (video.__rtmpWebReceiveTimer) {
|
|
video.__rtmpWebReceiveTimer = null;
|
|
}
|
|
}
|
|
|
|
function tryPlay() {
|
|
if (video && typeof video.play === 'function') {
|
|
video.play().catch(function () {
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
function markReceiving() {
|
|
if (webReceiveTimer) {
|
|
window.clearTimeout(webReceiveTimer);
|
|
webReceiveTimer = null;
|
|
}
|
|
if (video.__rtmpWebReceiveTimer) {
|
|
video.__rtmpWebReceiveTimer = null;
|
|
}
|
|
}
|
|
|
|
function scheduleReceiveRetry() {
|
|
if (isPreload || webReceiveRetryUsed || video.dataset.rtmpFailureHandled === '1') {
|
|
return;
|
|
}
|
|
if (webReceiveTimer) {
|
|
return;
|
|
}
|
|
webReceiveTimer = window.setTimeout(function () {
|
|
webReceiveTimer = null;
|
|
video.__rtmpWebReceiveTimer = null;
|
|
if (video.dataset.rtmpFailureHandled === '1' || webReceiveRetryUsed) {
|
|
return;
|
|
}
|
|
if (video.readyState >= 2) {
|
|
return;
|
|
}
|
|
webReceiveRetryUsed = true;
|
|
clearStartupTimer();
|
|
try {
|
|
if (video.__rtmpHls) {
|
|
video.__rtmpHls.destroy();
|
|
}
|
|
} catch (_error) {
|
|
// ignore cleanup errors
|
|
}
|
|
video.__rtmpHls = null;
|
|
requestSession();
|
|
}, 1200);
|
|
video.__rtmpWebReceiveTimer = webReceiveTimer;
|
|
}
|
|
|
|
function retryWebReceiveOrFail(message, silent) {
|
|
if (isPreload || video.dataset.rtmpFailureHandled === '1') {
|
|
return;
|
|
}
|
|
if (!webReceiveRetryUsed) {
|
|
webReceiveRetryUsed = true;
|
|
clearStartupTimer();
|
|
if (webReceiveTimer) {
|
|
window.clearTimeout(webReceiveTimer);
|
|
webReceiveTimer = null;
|
|
}
|
|
if (video.__rtmpWebReceiveTimer) {
|
|
video.__rtmpWebReceiveTimer = null;
|
|
}
|
|
window.setTimeout(function () {
|
|
requestSession();
|
|
}, 500);
|
|
return;
|
|
}
|
|
failPlayback(message, silent);
|
|
}
|
|
|
|
video.dataset.rtmpInitialized = '1';
|
|
video.muted = disableAudio;
|
|
video.controls = false;
|
|
video.playsInline = true;
|
|
video.autoplay = true;
|
|
|
|
bindRtmpVideoPlaceholder(video, placeholder);
|
|
video.addEventListener('canplay', markReceiving, { once: true });
|
|
video.addEventListener('playing', markReceiving, { once: true });
|
|
|
|
if (startupTimeoutMs > 0) {
|
|
startupTimer = window.setTimeout(function () {
|
|
startupTimer = null;
|
|
video.__rtmpStartupTimer = null;
|
|
failPlayback('Unable to load RTMP stream.', true);
|
|
}, startupTimeoutMs);
|
|
video.__rtmpStartupTimer = startupTimer;
|
|
}
|
|
|
|
function retryWhileProbing() {
|
|
if (probeRetryTimer || video.dataset.rtmpFailureHandled === '1') {
|
|
return;
|
|
}
|
|
probeRetryTimer = window.setTimeout(function () {
|
|
probeRetryTimer = null;
|
|
video.__rtmpProbeRetryTimer = null;
|
|
requestSession();
|
|
}, 500);
|
|
video.__rtmpProbeRetryTimer = probeRetryTimer;
|
|
}
|
|
|
|
function requestSession() {
|
|
fetch(getRtmpSessionUrl(sourceUrl, disableAudio), {
|
|
credentials: 'same-origin'
|
|
}).then(function (response) {
|
|
return response.json().catch(function () {
|
|
return null;
|
|
}).then(function (payload) {
|
|
return {
|
|
response: response,
|
|
payload: payload
|
|
};
|
|
});
|
|
}).then(function (result) {
|
|
if (!result || !result.response) {
|
|
throw new Error('Unable to initialize RTMP stream.');
|
|
}
|
|
|
|
var response = result.response;
|
|
var payload = result.payload || null;
|
|
var playlistUrl = payload && payload.playlistUrl ? String(payload.playlistUrl).trim() : '';
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 503 && payload && payload.probing) {
|
|
setRtmpAvailability(sourceUrl, disableAudio, null);
|
|
retryWhileProbing();
|
|
return null;
|
|
}
|
|
setRtmpAvailability(sourceUrl, disableAudio, false);
|
|
retryWebReceiveOrFail('Unable to load RTMP stream.', true);
|
|
return null;
|
|
}
|
|
|
|
setRtmpAvailability(sourceUrl, disableAudio, true);
|
|
if (!playlistUrl) {
|
|
throw new Error('RTMP playlist URL was not returned.');
|
|
}
|
|
|
|
if (window.Hls && window.Hls.isSupported && window.Hls.isSupported()) {
|
|
var hls = new window.Hls({
|
|
enableWorker: true,
|
|
liveSyncDurationCount: 6,
|
|
liveMaxLatencyDurationCount: 12,
|
|
maxBufferLength: 30,
|
|
maxLiveSyncPlaybackRate: 1,
|
|
backBufferLength: 60
|
|
});
|
|
video.__rtmpHls = hls;
|
|
hls.attachMedia(video);
|
|
hls.on(window.Hls.Events.MEDIA_ATTACHED, function () {
|
|
hls.loadSource(playlistUrl);
|
|
scheduleReceiveRetry();
|
|
});
|
|
hls.on(window.Hls.Events.MANIFEST_PARSED, function () {
|
|
tryPlay();
|
|
});
|
|
hls.on(window.Hls.Events.ERROR, function (_event, data) {
|
|
if (data && data.fatal) {
|
|
clearStartupTimer();
|
|
setRtmpAvailability(sourceUrl, disableAudio, false);
|
|
try {
|
|
hls.destroy();
|
|
} catch (_error) {
|
|
// ignore cleanup errors
|
|
}
|
|
video.__rtmpHls = null;
|
|
retryWebReceiveOrFail('RTMP playback failed.', true);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (video.canPlayType && video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
video.src = playlistUrl;
|
|
tryPlay();
|
|
scheduleReceiveRetry();
|
|
return;
|
|
}
|
|
|
|
failPlayback('RTMP playback is not supported in this browser.', true);
|
|
return null;
|
|
}).catch(function () {
|
|
clearStartupTimer();
|
|
setRtmpAvailability(sourceUrl, disableAudio, false);
|
|
retryWebReceiveOrFail('Unable to load RTMP stream.', true);
|
|
});
|
|
}
|
|
|
|
requestSession();
|
|
}
|
|
|
|
function syncRtmpRegions(root) {
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
var videos = root.querySelectorAll('video[data-rtmp-source]');
|
|
Array.prototype.forEach.call(videos, function (video) {
|
|
if (!video || video.dataset.rtmpInitialized === '1') {
|
|
return;
|
|
}
|
|
|
|
var sourceUrl = String(video.dataset.rtmpSource || '').trim();
|
|
var disableAudio = String(video.dataset.rtmpDisableAudio || '1') !== '0';
|
|
var region = video.parentNode;
|
|
var placeholder = region ? region.querySelector('.template-region-rtmp-placeholder') : null;
|
|
|
|
if (!sourceUrl) {
|
|
if (placeholder) {
|
|
placeholder.textContent = 'RTMP stream';
|
|
}
|
|
return;
|
|
}
|
|
|
|
var skipUnavailable = String(video.dataset.rtmpSkipUnavailable || '0') === '1';
|
|
var startupTimer = null;
|
|
|
|
if (skipUnavailable) {
|
|
var preflightStatus = getRtmpAvailability(sourceUrl, disableAudio);
|
|
if (preflightStatus && !isRtmpAvailabilityStale(preflightStatus) && preflightStatus.available === false) {
|
|
failPlayback('Unable to load RTMP stream.', true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
function failPlayback(message, silent) {
|
|
if (video.dataset.rtmpFailureHandled === '1') {
|
|
return;
|
|
}
|
|
video.dataset.rtmpFailureHandled = '1';
|
|
if (skipUnavailable && typeof handleRtmpPlaybackFailure === 'function' && handleRtmpPlaybackFailure(message, { silent: Boolean(silent) })) {
|
|
return;
|
|
}
|
|
if (placeholder) {
|
|
placeholder.style.display = 'flex';
|
|
placeholder.textContent = message;
|
|
}
|
|
}
|
|
|
|
startRtmpPlayback(video, sourceUrl, disableAudio, skipUnavailable, placeholder, false);
|
|
});
|
|
}
|
|
|
|
function destroyRtmpRegions(root) {
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
var videos = root.querySelectorAll('video[data-rtmp-source]');
|
|
Array.prototype.forEach.call(videos, function (video) {
|
|
if (video.__rtmpStartupTimer) {
|
|
window.clearTimeout(video.__rtmpStartupTimer);
|
|
video.__rtmpStartupTimer = null;
|
|
}
|
|
if (video.__rtmpProbeRetryTimer) {
|
|
window.clearTimeout(video.__rtmpProbeRetryTimer);
|
|
video.__rtmpProbeRetryTimer = null;
|
|
}
|
|
if (video.__rtmpWebReceiveTimer) {
|
|
window.clearTimeout(video.__rtmpWebReceiveTimer);
|
|
video.__rtmpWebReceiveTimer = null;
|
|
}
|
|
if (video.__rtmpHls) {
|
|
try {
|
|
video.__rtmpHls.destroy();
|
|
} catch (_error) {
|
|
// ignore cleanup errors
|
|
}
|
|
video.__rtmpHls = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
registry.register('rtmp', {
|
|
renderRegion: renderRtmpRegion
|
|
}); |