Add parent page navigation and PDF support
- PiP now controls parent page: clicking prev/next in PiP navigates the parent page URL - Add 'Go to Page' button (G key) to open current product page - Add PDF support: renders PDFs in iframe, opens in pop-out (not PiP) - PDFs and images fall back to pop-out player window - Pass product URLs through playerData for navigation - Audio gets small floating player, video gets PiP, PDFs get window
This commit is contained in:
parent
962a89c3a6
commit
f0b23ec0ff
6 changed files with 83 additions and 9 deletions
|
|
@ -62,10 +62,11 @@ DEFAULT_BUNDLE_METADATA = unicode(
|
||||||
VIDEO_EXTENSIONS = ["mp4", "webm", "mkv", "avi", "mov", "wmv", "flv", "m4v", "ogv"]
|
VIDEO_EXTENSIONS = ["mp4", "webm", "mkv", "avi", "mov", "wmv", "flv", "m4v", "ogv"]
|
||||||
AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "wma", "opus"]
|
AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "wma", "opus"]
|
||||||
IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"]
|
IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"]
|
||||||
|
DOCUMENT_EXTENSIONS = ["pdf"]
|
||||||
|
|
||||||
|
|
||||||
def get_media_type(extension):
|
def get_media_type(extension):
|
||||||
"""Returns 'video', 'audio', 'image', or None based on file extension."""
|
"""Returns 'video', 'audio', 'image', 'document', or None based on file extension."""
|
||||||
if not extension:
|
if not extension:
|
||||||
return None
|
return None
|
||||||
ext = extension.lower()
|
ext = extension.lower()
|
||||||
|
|
@ -75,6 +76,8 @@ def get_media_type(extension):
|
||||||
return "audio"
|
return "audio"
|
||||||
elif ext in IMAGE_EXTENSIONS:
|
elif ext in IMAGE_EXTENSIONS:
|
||||||
return "image"
|
return "image"
|
||||||
|
elif ext in DOCUMENT_EXTENSIONS:
|
||||||
|
return "document"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,20 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
toggleAutoResize();
|
toggleAutoResize();
|
||||||
break;
|
break;
|
||||||
|
case 'g':
|
||||||
|
case 'G':
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof goToProductPage === 'function') {
|
||||||
|
goToProductPage();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
case 'P':
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof togglePip === 'function') {
|
||||||
|
togglePip();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,11 @@ async function loadMedia(mediaElement, productId) {
|
||||||
const match = scriptContent.match(/window\.playerData\s*=\s*(\{[^}]+\})/);
|
const match = scriptContent.match(/window\.playerData\s*=\s*(\{[^}]+\})/);
|
||||||
if (match) {
|
if (match) {
|
||||||
currentPlayerData = eval('(' + match[1] + ')');
|
currentPlayerData = eval('(' + match[1] + ')');
|
||||||
|
|
||||||
|
// Navigate parent page to show the new product
|
||||||
|
if (currentPlayerData.productUrl) {
|
||||||
|
window.history.pushState({}, '', currentPlayerData.productUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +180,7 @@ async function openMediaPlayer(productId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter Picture-in-Picture mode
|
// Enter Picture-in-Picture mode (only for video)
|
||||||
if (document.pictureInPictureEnabled && media.tagName === 'VIDEO') {
|
if (document.pictureInPictureEnabled && media.tagName === 'VIDEO') {
|
||||||
await media.play();
|
await media.play();
|
||||||
await media.requestPictureInPicture();
|
await media.requestPictureInPicture();
|
||||||
|
|
@ -199,9 +204,13 @@ async function openMediaPlayer(productId) {
|
||||||
media.addEventListener('leavepictureinpicture', () => {
|
media.addEventListener('leavepictureinpicture', () => {
|
||||||
document.body.removeChild(media);
|
document.body.removeChild(media);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (media.tagName === 'AUDIO') {
|
||||||
// Fallback: keep small floating player on page
|
// Audio: keep small floating player on page
|
||||||
media.play();
|
media.play();
|
||||||
|
} else {
|
||||||
|
// For non-media (PDFs, images), open in pop-out player window
|
||||||
|
document.body.removeChild(media);
|
||||||
|
window.open(`/player/${productId}`, 'mediaPlayer', 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no,scrollbars=no');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error opening media player:', err);
|
console.error('Error opening media player:', err);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,11 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For documents (PDFs), fill the viewport */
|
||||||
|
iframe#media {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile optimizations */
|
/* Mobile optimizations */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.controls-grid {
|
.controls-grid {
|
||||||
|
|
@ -186,6 +191,10 @@
|
||||||
</audio>
|
</audio>
|
||||||
{% elif media_type == 'image' %}
|
{% elif media_type == 'image' %}
|
||||||
<img id="media" src="{{ presigned_url }}" alt="{{ product.title }}" />
|
<img id="media" src="{{ presigned_url }}" alt="{{ product.title }}" />
|
||||||
|
{% elif media_type == 'document' %}
|
||||||
|
<iframe id="media" src="{{ presigned_url }}" frameborder="0">
|
||||||
|
Your browser does not support PDF viewing.
|
||||||
|
</iframe>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="product-title">{{ product.title }}</div>
|
<div class="product-title">{{ product.title }}</div>
|
||||||
|
|
@ -214,8 +223,8 @@
|
||||||
|
|
||||||
<!-- Row 3: More options -->
|
<!-- Row 3: More options -->
|
||||||
<button id="autoResizeBtn" onclick="toggleAutoResize()" title="Auto-Resize Window (R)">↔ Resize</button>
|
<button id="autoResizeBtn" onclick="toggleAutoResize()" title="Auto-Resize Window (R)">↔ Resize</button>
|
||||||
|
<button onclick="goToProductPage()" title="Go to Product Page (G)">🔗 Page</button>
|
||||||
<button onclick="window.close()" title="Close Window (ESC)">✕ Close</button>
|
<button onclick="window.close()" title="Close Window (ESC)">✕ Close</button>
|
||||||
<div></div><!-- Empty cell for grid alignment -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -226,8 +235,24 @@
|
||||||
window.playerData = {
|
window.playerData = {
|
||||||
prevProductId: {{ ('"%s"' % prev_product_id) if prev_product_id else 'null' }},
|
prevProductId: {{ ('"%s"' % prev_product_id) if prev_product_id else 'null' }},
|
||||||
nextProductId: {{ ('"%s"' % next_product_id) if next_product_id else 'null' }},
|
nextProductId: {{ ('"%s"' % next_product_id) if next_product_id else 'null' }},
|
||||||
mediaType: "{{ media_type }}"
|
mediaType: "{{ media_type }}",
|
||||||
|
productId: "{{ product.id }}",
|
||||||
|
productUrl: "{{ product_url }}",
|
||||||
|
prevProductUrl: {{ ('"%s"' % prev_product_url) if prev_product_url else 'null' }},
|
||||||
|
nextProductUrl: {{ ('"%s"' % next_product_url) if next_product_url else 'null' }}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Go to the product page
|
||||||
|
function goToProductPage() {
|
||||||
|
if (window.opener && !window.opener.closed) {
|
||||||
|
// If opened from another window, navigate that window
|
||||||
|
window.opener.location.href = window.playerData.productUrl;
|
||||||
|
window.opener.focus();
|
||||||
|
} else {
|
||||||
|
// Otherwise open in new tab
|
||||||
|
window.open(window.playerData.productUrl, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,11 @@ async function loadMedia(mediaElement, productId) {
|
||||||
const match = scriptContent.match(/window\.playerData\s*=\s*(\{[^}]+\})/);
|
const match = scriptContent.match(/window\.playerData\s*=\s*(\{[^}]+\})/);
|
||||||
if (match) {
|
if (match) {
|
||||||
currentPlayerData = eval('(' + match[1] + ')');
|
currentPlayerData = eval('(' + match[1] + ')');
|
||||||
|
|
||||||
|
// Navigate parent page to show the new product
|
||||||
|
if (currentPlayerData.productUrl) {
|
||||||
|
window.history.pushState({}, '', currentPlayerData.productUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +258,7 @@ async function openMediaPlayer(productId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter Picture-in-Picture mode
|
// Enter Picture-in-Picture mode (only for video)
|
||||||
if (document.pictureInPictureEnabled && media.tagName === 'VIDEO') {
|
if (document.pictureInPictureEnabled && media.tagName === 'VIDEO') {
|
||||||
await media.play();
|
await media.play();
|
||||||
await media.requestPictureInPicture();
|
await media.requestPictureInPicture();
|
||||||
|
|
@ -277,9 +282,13 @@ async function openMediaPlayer(productId) {
|
||||||
media.addEventListener('leavepictureinpicture', () => {
|
media.addEventListener('leavepictureinpicture', () => {
|
||||||
document.body.removeChild(media);
|
document.body.removeChild(media);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (media.tagName === 'AUDIO') {
|
||||||
// Fallback: keep small floating player on page
|
// Audio: keep small floating player on page
|
||||||
media.play();
|
media.play();
|
||||||
|
} else {
|
||||||
|
// For non-media (PDFs, images), open in pop-out player window
|
||||||
|
document.body.removeChild(media);
|
||||||
|
window.open(`/player/${productId}`, 'mediaPlayer', 'width=800,height=600,menubar=no,toolbar=no,location=no,status=no,scrollbars=no');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error opening media player:', err);
|
console.error('Error opening media player:', err);
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,17 @@ def player(request):
|
||||||
if current_index < len(media_products) - 1:
|
if current_index < len(media_products) - 1:
|
||||||
next_product_id = str(media_products[current_index + 1].id)
|
next_product_id = str(media_products[current_index + 1].id)
|
||||||
|
|
||||||
|
# Get product URLs for navigation
|
||||||
|
product_url = product.absolute_url(request)
|
||||||
|
prev_product_url = None
|
||||||
|
next_product_url = None
|
||||||
|
|
||||||
|
if prev_product_id and current_index is not None and current_index > 0:
|
||||||
|
prev_product_url = media_products[current_index - 1].absolute_url(request)
|
||||||
|
|
||||||
|
if next_product_id and current_index is not None and current_index < len(media_products) - 1:
|
||||||
|
next_product_url = media_products[current_index + 1].absolute_url(request)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"product": product,
|
"product": product,
|
||||||
"presigned_url": presigned_url,
|
"presigned_url": presigned_url,
|
||||||
|
|
@ -112,4 +123,7 @@ def player(request):
|
||||||
"extension": extension,
|
"extension": extension,
|
||||||
"prev_product_id": prev_product_id,
|
"prev_product_id": prev_product_id,
|
||||||
"next_product_id": next_product_id,
|
"next_product_id": next_product_id,
|
||||||
|
"product_url": product_url,
|
||||||
|
"prev_product_url": prev_product_url,
|
||||||
|
"next_product_url": next_product_url,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue