Add PiP support to content.j2 template (was only in product.j2)

This commit is contained in:
russell@unturf.com 2026-01-22 10:09:12 -05:00
parent 453334f3e4
commit 6ec2a97f34

View file

@ -37,11 +37,11 @@
{% set video_extensions = ["mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v"] %}
{% if product.extensions.get("product") in video_extensions %}
{% if request.popout_player_enabled and product.visibility == 1 %}
<a href="javascript:void(0)" onclick="openMediaPlayer('{{ product.id }}')" class="video-thumbnail-container">
<a href="javascript:void(0)" onclick="openPipPlayer('{{ product.id }}')" class="video-thumbnail-container">
<img src="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main" />
<div class="video-play-overlay"></div>
</a>
<div class="video-click-to-play">click &#9654; to play in pop-out player</div>
<div class="video-click-to-play">click ▶ to play in picture-in-picture (right-click for always-on-top)</div>
{% else %}
<a target="_blank" href="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/product?ts={{ product.updated_timestamp }}" class="video-thumbnail-container">
<img src="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main" />
@ -96,10 +96,128 @@
</section>
<script>
function openMediaPlayer(productId) {
// Open pop-out player window (draggable and resizable)
const features = 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no';
window.open(`/player/${productId}`, 'mediaPlayer', features);
// PiP state
let pipVideo = null;
let pipData = null;
// Open native PiP with Media Session controls for prev/next
async function openPipPlayer(productId) {
console.log('=== Opening Native PiP for product:', productId, '===');
try {
// Fetch video data from JSON endpoint
const response = await fetch(`/player/${productId}/json`);
const data = await response.json();
console.log('Video data:', data);
if (!data.success || data.error) {
throw new Error(data.error || 'Failed to load video data');
}
// Create or reuse hidden video element
if (!pipVideo) {
pipVideo = document.createElement('video');
pipVideo.id = 'pipVideo';
pipVideo.style.cssText = 'position:fixed;bottom:10px;right:10px;width:320px;height:180px;z-index:9999;display:none;';
pipVideo.controls = true;
pipVideo.autoplay = true;
document.body.appendChild(pipVideo);
// Handle video end - auto advance
pipVideo.addEventListener('ended', async () => {
console.log('PiP video ended');
if (pipData && pipData.nextProductId) {
console.log('Auto-advancing to:', pipData.nextProductId);
await loadPipVideo(pipData.nextProductId);
}
});
// Handle leaving PiP
pipVideo.addEventListener('leavepictureinpicture', () => {
console.log('Left PiP mode');
pipVideo.style.display = 'none';
});
}
// Store current data
pipData = data;
// Load video
pipVideo.src = data.videoUrl;
pipVideo.style.display = 'block';
// Set up Media Session for prev/next controls in PiP
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: data.title,
artist: 'Media Player',
album: ''
});
// Previous track handler
navigator.mediaSession.setActionHandler('previoustrack', async () => {
console.log('Media Session: previous track');
if (pipData && pipData.prevProductId) {
await loadPipVideo(pipData.prevProductId);
}
});
// Next track handler
navigator.mediaSession.setActionHandler('nexttrack', async () => {
console.log('Media Session: next track');
if (pipData && pipData.nextProductId) {
await loadPipVideo(pipData.nextProductId);
}
});
}
// Enter native PiP mode
console.log('Requesting native PiP...');
await pipVideo.play();
await pipVideo.requestPictureInPicture();
console.log('Native PiP active - right-click for always-on-top option');
} catch (err) {
console.error('Failed to open PiP:', err);
alert('PiP failed: ' + err.message);
}
}
// Load new video while staying in PiP
async function loadPipVideo(productId) {
console.log('Loading video in PiP:', productId);
try {
const response = await fetch(`/player/${productId}/json`);
const data = await response.json();
if (!data.success || data.error) {
console.error('Failed to load:', data.error);
return;
}
// Update stored data
pipData = data;
// Update video source (stays in PiP)
pipVideo.src = data.videoUrl;
await pipVideo.play();
// Update Media Session metadata
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: data.title,
artist: 'Media Player',
album: ''
});
}
console.log('Now playing:', data.title);
} catch (err) {
console.error('Failed to load video:', err);
}
}
</script>