diff --git a/packages/core/src/WebPlayer.ts b/packages/core/src/WebPlayer.ts index 152ec55..435b3d1 100644 --- a/packages/core/src/WebPlayer.ts +++ b/packages/core/src/WebPlayer.ts @@ -12,6 +12,7 @@ export interface IWebPlayerOptions { disablePlayerSizeLevelCap?: boolean; iceServers?: RTCIceServer[]; enableCloudflareWhepBeta?: boolean; + enableSgai?: boolean; } export enum ReadyState { @@ -26,7 +27,7 @@ export default class WebPlayer extends EventEmitter { private _readyState: ReadyState = ReadyState.UNREADY; private tech: BaseTech; - private opts: IWebPlayerOptions; + public opts: IWebPlayerOptions; public video: HTMLVideoElement; public currentSrc?: string; @@ -83,6 +84,13 @@ export default class WebPlayer extends EventEmitter { private async loadTech(): Promise { if (this.manifestType === ManifestType.HLS) { + if (this.opts.enableSgai) { + // SGAI mode: always use hls.js for HLS interstitial support (including Safari) + const HlsJsTech = (await import('./tech/HlsJsTech')).default; + if (HlsJsTech.isSupported()) { + return HlsJsTech; + } + } if (canPlayManifestType(ManifestType.HLS) && isSafari()) { return BaseTech; } diff --git a/packages/core/src/tech/HlsJsTech.ts b/packages/core/src/tech/HlsJsTech.ts index 35f9053..c540fa6 100644 --- a/packages/core/src/tech/HlsJsTech.ts +++ b/packages/core/src/tech/HlsJsTech.ts @@ -7,6 +7,7 @@ const DEFAULT_CONFIG = { capLevelOnFPSDrop: true, capLevelToPlayerSize: true, enableInterstitialPlayback: true, + preferManagedMediaSource: true, }; const LIVE_EDGE = 5; // seconds from liveEdge @@ -595,7 +596,7 @@ export default class HlsJsTech extends BaseTech { for (const url of urls) { const trackingKey = `${url}-${eventType}`; if (this.interstitialTrackingFired.has(trackingKey)) { - return; // if already fired, duplicate + continue; // skip this URL, check remaining } this.interstitialTrackingFired.add(trackingKey); diff --git a/packages/demo/src/index.js b/packages/demo/src/index.js index f98f897..dfd1a3f 100644 --- a/packages/demo/src/index.js +++ b/packages/demo/src/index.js @@ -83,6 +83,7 @@ async function main() { const sgaiControls = document.querySelector('#sgai-controls'); const sgaiAdBreakButton = document.querySelector('#sgai-adbreak-button'); const sgaiDurationInput = document.querySelector('#sgai-duration'); + const sgaiCheckbox = document.querySelector('#sgai-checkbox'); renderExampleButtons(); if (!manifestInput.value) { @@ -97,7 +98,7 @@ async function main() { const searchParams = new URL(window.location.href).searchParams; const root = document.querySelector('#player'); - const video = document.createElement('video'); + let video = document.createElement('video'); root.appendChild(video); const snackbar = document.querySelector('#snackbar'); @@ -127,12 +128,13 @@ async function main() { } // Comment out this if you want to demo the player package - const player = new WebPlayer({ - video: video, + let player = new WebPlayer({ + video: video, iceServers: iceServers, enableCloudflareWhepBeta: process.env.CLOUDFLARE_BETA === "true", + enableSgai: sgaiCheckbox.checked, }); - renderEyevinnSkin({ + let skin = renderEyevinnSkin({ root, player, }); @@ -147,6 +149,7 @@ async function main() { function updateSgaiControls(manifestUrl) { if (manifestUrl === SGAI_STREAM_URL) { sgaiControls.style.display = 'block'; + sgaiCheckbox.checked = true; } else { sgaiControls.style.display = 'none'; } @@ -175,15 +178,33 @@ async function main() { } } - async function load() { + async function load() { try { if (epasUrlInput.value) { playerAnalytics = new PlayerAnalyticsConnector( epasUrlInput.value ); } - - player.reset(); + + // Recreate player if SGAI option changed + const wantSgai = sgaiCheckbox.checked; + if (wantSgai !== player.opts?.enableSgai) { + player.destroy(); + // Remove old video element and create fresh one + const newVideo = document.createElement('video'); + video.parentNode.replaceChild(newVideo, video); + video = newVideo; + player = new WebPlayer({ + video: video, + iceServers: iceServers, + enableCloudflareWhepBeta: process.env.CLOUDFLARE_BETA === "true", + enableSgai: wantSgai, + }); + player.on('*', () => {}); // re-init event forwarding + skin = renderEyevinnSkin({ root, player }); + } else { + player.reset(); + } try { playerAnalytics && await playerAnalytics.init({ diff --git a/packages/demo/src/style.css b/packages/demo/src/style.css index ddc2283..d9f7b87 100644 --- a/packages/demo/src/style.css +++ b/packages/demo/src/style.css @@ -36,15 +36,25 @@ video { .input-section .input-buttons .input-button, .input-section .input-buttons .autoplay-wrapper { font-size: 1em; - width: 10.5em; height: 2.15em; margin: 0.5em 0.3em; display: flex; align-items: center; justify-content: center; +} + +.input-section .input-buttons .input-button { + width: 10.5em; min-width: 10.3em; } +.input-section .input-buttons .autoplay-wrapper { + width: auto; + min-width: auto; + white-space: nowrap; + padding: 0 0.3em; +} + @media (max-width: 790px) { .input-section .input-buttons .input-button, .input-section .input-buttons .autoplay-wrapper { width: 47%; diff --git a/packages/demo/src/template.html b/packages/demo/src/template.html index 14388d4..0cf5243 100644 --- a/packages/demo/src/template.html +++ b/packages/demo/src/template.html @@ -44,6 +44,10 @@

WebPlayer@<%= htmlWebpackPlugin.options.title %>

+
+ + +