diff --git a/make_post_sell/templates/product.j2 b/make_post_sell/templates/product.j2
index 56c4124..d4b8447 100644
--- a/make_post_sell/templates/product.j2
+++ b/make_post_sell/templates/product.j2
@@ -37,11 +37,14 @@
{% set video_extensions = ["mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v"] %}
{% if signed_get_object_url and product.extensions.get("product") in video_extensions %}
{% if request.popout_player_enabled and product.visibility == 1 and "preview" in product.extensions %}
-
-
-
-
-
click ▶ to play in pop-out player
+
+ click ▶ for pop-out or 📺 for picture-in-picture
{% else %}
@@ -179,6 +182,191 @@ function openMediaPlayer(productId) {
const features = 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no';
window.open(`/player/${productId}`, 'mediaPlayer', features);
}
+
+// Open directly in Document Picture-in-Picture mode
+async function openPipPlayer(productId) {
+ console.log('Opening PiP for product:', productId);
+
+ // Check for Document PiP support
+ if (!('documentPictureInPicture' in window)) {
+ alert('Document Picture-in-Picture is not supported in this browser.\n\nPlease use Chrome 111+ or Edge 111+.\n\nFalling back to pop-up window.');
+ openMediaPlayer(productId);
+ return;
+ }
+
+ try {
+ // Fetch the player page to get video URL and metadata
+ console.log('Fetching player page...');
+ const response = await fetch(`/player/${productId}`);
+ const html = await response.text();
+
+ // Parse HTML to extract video URL and metadata
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+
+ // Find video element and get source
+ const videoEl = doc.querySelector('video');
+ if (!videoEl || !videoEl.src) {
+ console.error('No video element or source found');
+ alert('Could not find video. Opening in pop-up window instead.');
+ openMediaPlayer(productId);
+ return;
+ }
+
+ const videoSrc = videoEl.src;
+ console.log('Video source:', videoSrc);
+
+ // Extract player data from script tag
+ const scriptContent = doc.querySelector('script:not([src])').textContent;
+ const playerDataMatch = scriptContent.match(/window\.playerData\s*=\s*({[^}]+})/);
+ let playerData = { prevProductId: null, nextProductId: null };
+ if (playerDataMatch) {
+ try {
+ // Clean up the matched content and parse it
+ const dataStr = playerDataMatch[1]
+ .replace(/"/g, '"')
+ .replace(/'/g, '"')
+ .replace(/(\w+):/g, '"$1":')
+ .replace(/null/g, 'null');
+ playerData = JSON.parse(dataStr);
+ console.log('Parsed player data:', playerData);
+ } catch (e) {
+ console.warn('Could not parse player data:', e);
+ }
+ }
+
+ // Get product title
+ const titleEl = doc.querySelector('.product-title');
+ const productTitle = titleEl ? titleEl.textContent : 'Video';
+
+ // Open Document PiP window with custom controls
+ console.log('Opening Document PiP window...');
+ const pipWindow = await window.documentPictureInPicture.requestWindow({
+ width: 640,
+ height: 420 // 360 for 16:9 video + 60 for controls
+ });
+
+ console.log('Document PiP window opened');
+
+ // Add styles to PiP window
+ const pipStyle = pipWindow.document.createElement('style');
+ pipStyle.textContent = `
+ body {
+ margin: 0;
+ padding: 0;
+ background: #000;
+ overflow: hidden;
+ font-family: sans-serif;
+ }
+ .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
+ const container = pipWindow.document.createElement('div');
+ container.className = 'pip-container';
+
+ // Create video element
+ const video = pipWindow.document.createElement('video');
+ video.className = 'pip-video';
+ video.src = videoSrc;
+ video.controls = true;
+ video.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 = !playerData.prevProductId;
+ if (playerData.prevProductId) {
+ prevBtn.onclick = () => {
+ window.location.href = `/product/${playerData.prevProductId}`;
+ };
+ }
+
+ // Title
+ const title = pipWindow.document.createElement('div');
+ title.className = 'pip-title';
+ title.textContent = productTitle;
+
+ // Next button
+ const nextBtn = pipWindow.document.createElement('button');
+ nextBtn.textContent = 'Next â–¶';
+ nextBtn.disabled = !playerData.nextProductId;
+ if (playerData.nextProductId) {
+ nextBtn.onclick = () => {
+ window.location.href = `/product/${playerData.nextProductId}`;
+ };
+ }
+
+ controls.appendChild(prevBtn);
+ controls.appendChild(title);
+ controls.appendChild(nextBtn);
+
+ container.appendChild(video);
+ container.appendChild(controls);
+ pipWindow.document.body.appendChild(container);
+
+ // Auto-advance when video ends
+ video.addEventListener('ended', () => {
+ if (playerData.nextProductId) {
+ window.location.href = `/product/${playerData.nextProductId}`;
+ }
+ });
+
+ console.log('Document PiP setup complete');
+
+ } catch (err) {
+ console.error('Failed to open Document PiP:', err);
+ alert('Failed to open Picture-in-Picture: ' + err.message + '\n\nOpening in pop-up window instead.');
+ openMediaPlayer(productId);
+ }
+}
{%- endblock -%}