From d2446175546bdc80962e73db7798aeab7fb4543c Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Wed, 21 Jan 2026 12:49:44 -0500 Subject: [PATCH] Add native Picture-in-Picture support and debug ended event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picture-in-Picture (PiP) improvements: - Add native browser PiP button for videos (P key or 📺 button) - PiP has true OS-level always-on-top when you right-click and select "Keep on top" - PiP works on mobile and desktop with browser's native implementation - Button shows on mobile (PiP is useful on mobile devices) - Active state toggles button text between "📺 PiP" and "📺 Exit PiP" Debug 'ended' event not firing: - Add explicit ended event listener with console logging - Log when media starts playing - Log playback progress every 5 seconds - This will help identify why auto-advance isn't working The native PiP API provides better always-on-top than our window.focus() approach because it's implemented at the OS level by the browser. Co-Authored-By: Claude Sonnet 4.5 --- make_post_sell/static/js/player.js | 7 +++ make_post_sell/templates/player.j2 | 95 ++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/make_post_sell/static/js/player.js b/make_post_sell/static/js/player.js index ccf582a..4b1e835 100644 --- a/make_post_sell/static/js/player.js +++ b/make_post_sell/static/js/player.js @@ -381,6 +381,13 @@ goToProductPage(); } break; + case 'p': + case 'P': + e.preventDefault(); + if (typeof togglePip === 'function') { + togglePip(); + } + break; } } diff --git a/make_post_sell/templates/player.j2 b/make_post_sell/templates/player.j2 index edfa3c6..75f431f 100644 --- a/make_post_sell/templates/player.j2 +++ b/make_post_sell/templates/player.j2 @@ -72,10 +72,8 @@ } /* Hide desktop-only buttons on mobile */ - #pipBtn, #autoResizeBtn, - #alwaysOnTopBtn, - #fullscreenBtn { + #alwaysOnTopBtn { display: none; } @@ -231,8 +229,10 @@ + {% if media_type == 'video' %} + + {% endif %} - @@ -260,6 +260,41 @@ window.open(window.playerData.productUrl, '_blank'); } + // Toggle native Picture-in-Picture (true always-on-top via browser) + function togglePip() { + const media = document.getElementById('media'); + if (!media || media.tagName !== 'VIDEO') { + return; + } + + if (document.pictureInPictureElement) { + document.exitPictureInPicture().catch(err => { + console.error('Failed to exit PiP:', err); + }); + } else { + media.requestPictureInPicture().catch(err => { + console.error('Failed to enter PiP:', err); + }); + } + } + + // Update PiP button state + document.addEventListener('enterpictureinpicture', () => { + const pipBtn = document.getElementById('pipBtn'); + if (pipBtn) { + pipBtn.classList.add('active'); + pipBtn.textContent = '📺 Exit PiP'; + } + }); + + document.addEventListener('leavepictureinpicture', () => { + const pipBtn = document.getElementById('pipBtn'); + if (pipBtn) { + pipBtn.classList.remove('active'); + pipBtn.textContent = '📺 PiP'; + } + }); + // Mobile: tap to show/hide controls let hideTimeout; const isMobile = window.innerWidth <= 600; @@ -314,17 +349,51 @@ if (media && (media.tagName === 'VIDEO' || media.tagName === 'AUDIO')) { // Try to play muted first (allowed by most browsers) media.muted = true; - media.play().then(() => { - // Once playing, unmute after a short delay - setTimeout(() => { - media.muted = false; - }, 100); - }).catch(err => { - console.log('Autoplay failed:', err); - // User will need to tap play button - }); + const playPromise = media.play(); + if (playPromise !== undefined) { + playPromise.then(() => { + // Once playing, unmute after a short delay + setTimeout(() => { + media.muted = false; + }, 100); + }).catch(err => { + console.log('Autoplay failed:', err); + // User will need to tap play button + }); + } } } + + // Debug: Add ended event listener to verify it works + (function() { + const media = document.getElementById('media'); + if (media && (media.tagName === 'VIDEO' || media.tagName === 'AUDIO')) { + media.addEventListener('ended', function() { + console.log('Media ended event fired!'); + if (window.playerData && window.playerData.nextProductId) { + console.log('Navigating to next:', window.playerData.nextProductId); + window.location.href = '/player/' + window.playerData.nextProductId; + } else { + console.log('No next product available'); + } + }); + + // Debug: Log when media is playing + media.addEventListener('play', function() { + console.log('Media started playing'); + }); + + // Debug: Log time updates to verify playback + let lastLogTime = 0; + media.addEventListener('timeupdate', function() { + const currentTime = Math.floor(media.currentTime); + if (currentTime > lastLogTime && currentTime % 5 === 0) { + console.log('Playback at:', currentTime + 's / ' + Math.floor(media.duration) + 's'); + lastLogTime = currentTime; + } + }); + } + })();