Add PiP support to content.j2 template (was only in product.j2)
This commit is contained in:
parent
453334f3e4
commit
6ec2a97f34
1 changed files with 124 additions and 6 deletions
|
|
@ -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 ▶ 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>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue