diff --git a/make_post_sell/templates/player.j2 b/make_post_sell/templates/player.j2 index 75f431f..8ffe70d 100644 --- a/make_post_sell/templates/player.j2 +++ b/make_post_sell/templates/player.j2 @@ -230,7 +230,7 @@ {% if media_type == 'video' %} - + {% endif %} @@ -260,13 +260,209 @@ window.open(window.playerData.productUrl, '_blank'); } - // Toggle native Picture-in-Picture (true always-on-top via browser) - function togglePip() { + // Document Picture-in-Picture with custom controls + let pipWindow = null; + + async function togglePip() { const media = document.getElementById('media'); if (!media || media.tagName !== 'VIDEO') { return; } + // Check for Document Picture-in-Picture API support + if ('documentPictureInPicture' in window) { + if (pipWindow) { + // Close existing PiP window + pipWindow.close(); + pipWindow = null; + updatePipButtonState(false); + return; + } + + try { + // Calculate aspect ratio from video + const aspectRatio = media.videoWidth / media.videoHeight; + let width = 640; + let height = Math.round(width / aspectRatio); + + // Constrain to reasonable sizes + if (height > 480) { + height = 480; + width = Math.round(height * aspectRatio); + } + + // Open Document PiP window with custom content + pipWindow = await window.documentPictureInPicture.requestWindow({ + width: width, + height: height + 60 // Extra space for controls + }); + + updatePipButtonState(true); + + // Copy stylesheets to PiP window + const stylesheets = Array.from(document.styleSheets); + stylesheets.forEach(stylesheet => { + try { + const cssRules = Array.from(stylesheet.cssRules); + const style = pipWindow.document.createElement('style'); + cssRules.forEach(rule => { + style.textContent += rule.cssText; + }); + pipWindow.document.head.appendChild(style); + } catch (e) { + // External stylesheet, link it + const link = pipWindow.document.createElement('link'); + link.rel = 'stylesheet'; + link.href = stylesheet.href; + pipWindow.document.head.appendChild(link); + } + }); + + // Add PiP-specific styles + const pipStyle = pipWindow.document.createElement('style'); + pipStyle.textContent = ` + body { + margin: 0; + padding: 0; + background: #000; + overflow: hidden; + } + .pip-container { + display: grid; + grid-template-rows: 1fr auto; + width: 100vw; + height: 100vh; + } + .pip-video { + width: 100%; + height: 100%; + object-fit: contain; + } + .pip-controls { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 0.5rem; + padding: 0.5rem; + background: rgba(0,0,0,0.9); + align-items: center; + } + .pip-controls button { + background: rgba(255,255,255,0.2); + border: none; + color: white; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + } + .pip-controls button:hover:not(:disabled) { + background: rgba(255,255,255,0.4); + } + .pip-controls button:disabled { + opacity: 0.3; + cursor: not-allowed; + } + .pip-title { + color: white; + font-size: 0.9rem; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + `; + pipWindow.document.head.appendChild(pipStyle); + + // Build PiP content with navigation controls + const container = pipWindow.document.createElement('div'); + container.className = 'pip-container'; + + // Clone video element + const pipVideo = pipWindow.document.createElement('video'); + pipVideo.className = 'pip-video'; + pipVideo.src = media.src; + pipVideo.currentTime = media.currentTime; + pipVideo.controls = true; + pipVideo.autoplay = true; + + // Create controls + const controls = pipWindow.document.createElement('div'); + controls.className = 'pip-controls'; + + // Previous button + const prevBtn = pipWindow.document.createElement('button'); + prevBtn.textContent = '◀ Prev'; + prevBtn.disabled = !window.playerData.prevProductId; + prevBtn.onclick = () => { + if (window.playerData.prevProductId) { + window.location.href = '/player/' + window.playerData.prevProductId; + } + }; + + // Title + const title = pipWindow.document.createElement('div'); + title.className = 'pip-title'; + title.textContent = '{{ product.title }}'; + + // Next button + const nextBtn = pipWindow.document.createElement('button'); + nextBtn.textContent = 'Next ▶'; + nextBtn.disabled = !window.playerData.nextProductId; + nextBtn.onclick = () => { + if (window.playerData.nextProductId) { + window.location.href = '/player/' + window.playerData.nextProductId; + } + }; + + controls.appendChild(prevBtn); + controls.appendChild(title); + controls.appendChild(nextBtn); + + container.appendChild(pipVideo); + container.appendChild(controls); + pipWindow.document.body.appendChild(container); + + // Sync playback between main window and PiP + pipVideo.addEventListener('play', () => media.play()); + pipVideo.addEventListener('pause', () => media.pause()); + pipVideo.addEventListener('seeked', () => { + media.currentTime = pipVideo.currentTime; + }); + + // When PiP video ends, navigate to next + pipVideo.addEventListener('ended', () => { + if (window.playerData.nextProductId) { + window.location.href = '/player/' + window.playerData.nextProductId; + } + }); + + // Handle PiP window close + pipWindow.addEventListener('pagehide', () => { + pipWindow = null; + updatePipButtonState(false); + // Sync playback state back to main window + if (!pipVideo.paused) { + media.currentTime = pipVideo.currentTime; + media.play(); + } + }); + + // Pause main video (playback continues in PiP) + media.pause(); + + } catch (err) { + console.error('Failed to open Document PiP:', err); + // Fallback to regular PiP + fallbackToPip(media); + } + } else { + // Fallback to regular PiP for unsupported browsers + fallbackToPip(media); + } + } + + // Fallback to regular Picture-in-Picture API + function fallbackToPip(media) { if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(err => { console.error('Failed to exit PiP:', err); @@ -279,19 +475,29 @@ } // Update PiP button state - document.addEventListener('enterpictureinpicture', () => { + function updatePipButtonState(isActive) { const pipBtn = document.getElementById('pipBtn'); if (pipBtn) { - pipBtn.classList.add('active'); - pipBtn.textContent = '📺 Exit PiP'; + if (isActive) { + pipBtn.classList.add('active'); + pipBtn.textContent = '📺 Exit PiP'; + } else { + pipBtn.classList.remove('active'); + pipBtn.textContent = '📺 PiP'; + } + } + } + + // Listen for regular PiP events (fallback mode) + document.addEventListener('enterpictureinpicture', () => { + if (!pipWindow) { + updatePipButtonState(true); } }); document.addEventListener('leavepictureinpicture', () => { - const pipBtn = document.getElementById('pipBtn'); - if (pipBtn) { - pipBtn.classList.remove('active'); - pipBtn.textContent = '📺 PiP'; + if (!pipWindow) { + updatePipButtonState(false); } });