Merge pull request #1223 from PR0M3TH3AN/video-source-toggle-2596229844924260490

Add CDN/P2P Source Toggle to Video Player
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-14 20:09:31 -05:00
commit b9171e9e05
6 files changed
+243 -3

No files matched your search

+26
View File
@@ -129,6 +129,32 @@
class="video-modal__stats rounded-2xl border border-border border-opacity-20 bg-surface px-6 py-5 shadow-sm"
data-video-stats
>
<div class="video-modal__source-toggle-container mb-4" data-source-toggle-container hidden>
<div class="inline-flex rounded-lg bg-overlay-panel-soft p-1">
<button
type="button"
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors focus-ring"
data-source-toggle="url"
aria-pressed="true"
>
<span class="flex items-center gap-2">
<span>CDN</span>
<span class="text-xs opacity-70" data-active-servers-count>1 server</span>
</span>
</button>
<button
type="button"
class="px-3 py-1.5 text-xs font-medium rounded-md transition-colors focus-ring"
data-source-toggle="torrent"
aria-pressed="false"
>
<span class="flex items-center gap-2">
<span>P2P</span>
<span class="text-xs opacity-70" data-active-peers-count>0 peers</span>
</span>
</button>
</div>
</div>
<div class="video-modal__progress">
<progress
id="modalProgress"
+1 -1
View File
@@ -5,4 +5,4 @@
--player-modal-inline-padding,clamp(var(--space-md),4vw,var(--space-xl-plus))
);overflow:visible}#playerModal .watch-container.video-modal__similar{padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .video-modal__primary>.watch-container:first-child{padding-block-start:var(--player-modal-vertical-padding)}#playerModal .video-modal__primary>.watch-container:last-child,#playerModal .video-modal__secondary>.watch-container:last-child{padding-block-end:var(--player-modal-vertical-padding)}#playerModal .modal-nav .watch-container{max-width:none;margin-inline:0}#playerModal .modal-info-panel{display:flex;flex-direction:column;gap:var(--space-md-plus)}#playerModal h2#videoTitle{margin:0;font-size:clamp(var(--font-size-xl),calc(var(--font-size-modal-title-base) + .5vw),var(--font-size-xl-plus));font-weight:600;line-height:1.3;color:var(--color-text);min-width:0;overflow-wrap:break-word;word-break:break-word}@media (min-width:1024px){.video-modal__headline #videoTitle.video-modal__title{flex:1 1 0%;min-width:0;max-width:calc(100% - var(--space-3xl) - var(--space-md))}.video-modal__headline .video-modal__status{flex:0 0 auto;max-width:clamp(var(--space-xl),25%,var(--space-3xl));margin-left:auto}}#playerModal .modal-meta{font-size:var(--font-size-sm);line-height:1.4;color:var(--color-text-muted)}#playerModal .modal-view-count{font-weight:600;color:var(--color-text)}#playerModal .channel-card{align-items:center;gap:var(--space-md);padding:var(--space-md) var(--space-lg)}#playerModal .channel-card,#playerModal .player-modal__module{display:flex;border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}#playerModal .player-modal__module{flex-direction:column;gap:clamp(var(--space-2xs),1vw,var(--space-sm));width:100%;padding:clamp(var(--space-xs),1vw,var(--space-sm))}#playerModal .player-modal__module-title{margin:0;font-size:var(--font-size-lg);font-weight:600;line-height:1.3;color:var(--color-text)}#playerModal .player-modal__module-list{display:flex;flex-direction:column;gap:var(--space-sm);width:100%;margin:0;padding:0;list-style:none}#playerModal .video-modal__similar .player-modal__module{gap:clamp(var(--space-3xs),.6vw,var(--space-xs));padding:clamp(var(--space-2xs),.6vw,var(--space-xs));border-width:0;box-shadow:none;background-color:transparent}#playerModal .video-modal__similar .player-modal__module-title{padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-3xs),.6vw,var(--space-xs));padding-inline:clamp(var(--space-3xs),.6vw,var(--space-2xs));padding-bottom:clamp(var(--space-3xs),.6vw,var(--space-2xs))}#playerModal .player-modal__module-scroll{max-height:clamp(var(--player-modal-module-scroll-min-height),calc(100vh - var(--player-modal-vertical-padding)*2 - var(--space-2xl)),var(--player-modal-module-scroll-max-height));overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-gutter:stable;padding-inline-end:var(--space-xs)}#playerModal .video-modal__similar .player-modal__module-scroll{max-height:none;overflow:visible;padding-inline-end:0;scrollbar-gutter:auto}#playerModal .player-modal__module-scroll>li{scroll-margin-block:var(--space-xs)}@media (max-width:30rem){#playerModal .modal-nav .icon-button{width:var(--space-xl);height:var(--space-xl);min-width:var(--space-xl);min-height:var(--space-xl)}#playerModal .watch-container{padding-inline:var(--space-sm)}#playerModal .channel-card{gap:var(--space-sm);padding:var(--space-sm) var(--space-md)}}#playerModal .channel-card__avatar{flex-shrink:0;width:var(--avatar-size-lg);height:var(--avatar-size-lg);border-radius:var(--radius-full);overflow:hidden;background-color:var(--color-border-subtle)}#playerModal .channel-card__avatar img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}#playerModal .channel-card__name{margin:0;font-size:var(--font-size-lg);font-weight:600;color:var(--color-text)}#playerModal .channel-card__npub{margin:0;font-size:var(--font-size-sm);color:var(--color-text-muted)}#playerModal .description-panel{padding:var(--space-md-plus) var(--space-lg);border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}#playerModal .description-panel p{margin:0;font-size:var(--font-size-sm);line-height:1.6;color:var(--color-text)}.player-modal__content{position:relative;z-index:var(--z-modal-content);display:flex;flex:1 1 auto;width:100%;min-height:100%;flex-direction:column;overflow:visible;padding:0;margin:0;border:none;border-radius:0;background-color:var(--color-surface);box-shadow:none}@media (prefers-reduced-motion:reduce){.player-modal__content{transition-property:none}}#playerModal .modal-nav{position:sticky;top:0;z-index:var(--z-overlay-nav);--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) sLine truncated
--player-modal-primary-inline-padding-lg,clamp(var(--space-sm),2.75vw,var(--space-lg))
)}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem;min-width:0;min-height:0}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem;min-width:0;min-height:0;width:100%;align-self:stretch}.video-modal__similar{display:none}@media (min-width:1024px){#playerModal.video-modal__similar{display:block}.video-modal__similar{display:flex;flex-direction:column;gap:1.5rem;align-self:stretch;flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module{height:100%;display:flex;flex:1 1 auto;min-height:0;flex-direction:column}#playerModal .video-modal__similar .player-modal__module-list{flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module-scroll{height:100%;max-height:none;min-height:0}}#playerModal .player-modal__module-item{width:100%}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))!important}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media,#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))!important}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-2xs),.85vw,var(--space-sm))}.player-modal__similar-card{outline:2px solid transparent;outline-offset:2px}.player-modal__similar-card:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--surface-base);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1);--tw-ring-color:var(--color-accent)}#playerModal.player-modal__similar-card{display:block}.player-modal__similar-card{position:relative;displayLine truncated
)}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem;min-width:0;min-height:0}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem;min-width:0;min-height:0;width:100%;align-self:stretch}.video-modal__similar{display:none}@media (min-width:1024px){#playerModal.video-modal__similar{display:block}.video-modal__similar{display:flex;flex-direction:column;gap:1.5rem;align-self:stretch;flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module{height:100%;display:flex;flex:1 1 auto;min-height:0;flex-direction:column}#playerModal .video-modal__similar .player-modal__module-list{flex:1 1 auto;min-height:0}#playerModal .video-modal__similar .player-modal__module-scroll{height:100%;max-height:none;min-height:0}}#playerModal .player-modal__module-item{width:100%}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card){display:flex;width:100%;flex-direction:row;flex-wrap:nowrap;align-items:stretch;gap:var(--space-md)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media{width:clamp(var(--video-modal-item-media-min),18vw,var(--video-modal-item-media-max))!important}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__media,#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__media{flex-shrink:0;overflow:hidden;border-radius:var(--radius-lg)}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{padding:clamp(var(--space-sm),1.25vw,var(--space-md))!important}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__content{display:flex;min-width:0;flex:1 1 0%;flex-direction:column;gap:var(--space-sm)}#playerModal#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:block}#playerModal .player-modal__module-item>.\!card:not(.player-modal__similar-card) .video-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-size:var(--font-size-xs);line-height:var(--line-height-tight);color:var(--text-muted-strong)}#playerModal .video-modal__similar .player-modal__module-list{gap:clamp(var(--space-2xs),.85vw,var(--space-sm))}.player-modal__similar-card{outline:2px solid transparent;outline-offset:2px}.player-modal__similar-card:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--surface-base);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1);--tw-ring-color:var(--color-accent)}#playerModal.player-modal__similar-card{display:block}.player-modal__similar-card{position:relative;displayLine truncated
+27 -1
View File
@@ -390,6 +390,30 @@ class Application {
if (modalRoot) {
this.attachMoreMenuHandlers(modalRoot);
}
if (
this.videoModal &&
typeof this.videoModal.addEventListener === "function"
) {
this.videoModal.addEventListener("playback:switch-source", (event) => {
const detail = event?.detail || {};
const { source } = detail;
if (!source) {
return;
}
if (this.currentVideo) {
this.playVideoWithFallback({
url: this.currentVideo.url,
magnet: this.currentVideo.magnet,
forcedSource: source,
}).catch((error) => {
devLogger.warn(
"[app] Failed to switch playback source:",
error,
);
});
}
});
}
});
const uploadModalPromise = this.uploadModal
@@ -8209,7 +8233,7 @@ class Application {
* and falls back to WebTorrent when needed.
*/
async playVideoWithFallback(options = {}) {
const { url = "", magnet = "", trigger } = options || {};
const { url = "", magnet = "", trigger, forcedSource } = options || {};
const hasTrigger = Object.prototype.hasOwnProperty.call(
options || {},
"trigger"
@@ -8223,6 +8247,7 @@ class Application {
const requestSignature = JSON.stringify({
url: sanitizedUrl,
magnet: trimmedMagnet,
forcedSource,
});
const modalVideoIsConnected = (() => {
@@ -8374,6 +8399,7 @@ class Application {
this.playViaWebTorrent(magnetUri, options),
autoplay: () => this.autoplayModalVideo(),
unsupportedBtihMessage: UNSUPPORTED_BTITH_MESSAGE,
forcedSource,
});
this.activePlaybackSession = session;
+6 -1
View File
@@ -409,6 +409,7 @@ class PlaybackSession extends SimpleEventEmitter {
playViaWebTorrent,
autoplay,
unsupportedBtihMessage = DEFAULT_UNSUPPORTED_BTITH_MESSAGE,
forcedSource = null,
} = this.options;
this.result = { source: null };
@@ -555,7 +556,11 @@ class PlaybackSession extends SimpleEventEmitter {
return torrentInstance;
};
if (this.service.urlFirstEnabled && httpsUrl) {
if (
this.service.urlFirstEnabled &&
httpsUrl &&
forcedSource !== "torrent"
) {
this.emit("status", { message: "Checking hosted URL..." });
this.service.log(
`[playVideoWithFallback] Probing hosted URL ${httpsUrl} (readyState=${activeVideoEl.readyState} networkState=${activeVideoEl.networkState}).`
+92
View File
@@ -585,6 +585,14 @@ export class VideoModal {
this.modalSpeed = playerModal.querySelector("#modalSpeed") || null;
this.modalDownloaded =
playerModal.querySelector("#modalDownloaded") || null;
this.sourceToggleContainer =
playerModal.querySelector("[data-source-toggle-container]") || null;
this.sourceToggleButtons =
playerModal.querySelectorAll("[data-source-toggle]") || [];
this.activeServersLabel =
playerModal.querySelector("[data-active-servers-count]") || null;
this.activePeersLabel =
playerModal.querySelector("[data-active-peers-count]") || null;
this.setTorrentStatsVisibility(false);
this.videoTitle = playerModal.querySelector("#videoTitle") || null;
this.videoDescription =
@@ -2244,6 +2252,19 @@ export class VideoModal {
this.modalMoreBtn.addEventListener("click", this.handleModalMoreButtonClick);
}
if (this.sourceToggleButtons.length > 0) {
this.sourceToggleButtons.forEach((btn) => {
btn.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
const mode = btn.dataset.sourceToggle;
if (mode) {
this.handleSourceToggle(mode);
}
});
});
}
if (this.modalZapCloseBtn) {
this.modalZapCloseBtn.addEventListener("click", (event) => {
event?.preventDefault?.();
@@ -2562,6 +2583,67 @@ export class VideoModal {
this.dispatch("video:copy-magnet", { video: this.activeVideo });
}
handleSourceToggle(mode) {
if (!this.activeVideo || (mode !== "url" && mode !== "torrent")) {
return;
}
this.dispatch("playback:switch-source", {
video: this.activeVideo,
source: mode,
});
}
updateSourceAvailability(video) {
if (!this.sourceToggleContainer) {
return;
}
const hasUrl = Boolean(
typeof video?.url === "string" && video.url.trim()
);
const hasMagnet = Boolean(
(typeof video?.magnet === "string" && video.magnet.trim()) ||
video?.torrentSupported
);
if (hasUrl || hasMagnet) {
this.sourceToggleContainer.removeAttribute("hidden");
this.sourceToggleContainer.classList.remove("hidden");
} else {
this.sourceToggleContainer.setAttribute("hidden", "");
this.sourceToggleContainer.classList.add("hidden");
}
this.sourceToggleButtons.forEach((btn) => {
const mode = btn.dataset.sourceToggle;
if (mode === "url") {
btn.disabled = !hasUrl;
} else if (mode === "torrent") {
btn.disabled = !hasMagnet;
}
});
if (this.activeServersLabel) {
const count = hasUrl ? 1 : 0;
this.activeServersLabel.textContent =
count === 1 ? "1 server" : `${count} servers`;
}
}
updateSourceToggleState(activeSource) {
if (!this.sourceToggleButtons.length) {
return;
}
const current = activeSource === "torrent" ? "torrent" : "url";
this.sourceToggleButtons.forEach((btn) => {
const mode = btn.dataset.sourceToggle;
const isActive = mode === current;
btn.setAttribute("aria-pressed", isActive ? "true" : "false");
});
}
handleShareRequest(event) {
event?.preventDefault?.();
if (this.shareBtn?.disabled) {
@@ -2676,6 +2758,7 @@ export class VideoModal {
}
this.setGlobalModalState("player", true);
this.applyLoadingPoster();
this.updateSourceAvailability(this.activeVideo);
this.refreshActiveVideoModeration({ video: this.activeVideo });
}
@@ -3262,6 +3345,8 @@ export class VideoModal {
setTorrentStatsVisibility(shouldShow) {
const visible = Boolean(shouldShow);
this.updateSourceToggleState(visible ? "torrent" : "url");
const container = this.modalStatsContainer;
const stats = this.modalStats;
const toggle = (element, show) => {
@@ -3300,6 +3385,12 @@ export class VideoModal {
if (this.modalPeers) {
this.modalPeers.textContent = text || "";
}
if (this.activePeersLabel) {
const match = (text || "").match(/\d+/);
const count = match ? parseInt(match[0], 10) : 0;
this.activePeersLabel.textContent =
count === 1 ? "1 peer" : `${count} peers`;
}
}
updateSpeed(text) {
@@ -4076,6 +4167,7 @@ export class VideoModal {
this.updateCreator(creator);
}
this.updateSourceAvailability(this.activeVideo);
this.refreshActiveVideoModeration({ video: this.activeVideo });
}
+91
View File
@@ -0,0 +1,91 @@
from playwright.sync_api import sync_playwright
import time
def verify_source_toggle():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Navigate to the local server
page.goto("http://localhost:8080/index.html")
# Wait for the app to initialize (give it a moment)
page.wait_for_load_state("networkidle")
# We need to simulate opening the modal with a video that has both sources
# We'll use page.evaluate to interact with the Javascript context
# Assuming 'app' is globally available or we can instantiate VideoModal
page.evaluate("""
async () => {
// Mock a video object with both URL and Magnet
const mockVideo = {
id: 'test-video-123',
title: 'Test Video with Source Toggle',
url: 'https://example.com/video.mp4',
magnet: 'magnet:?xt=urn:btih:example',
torrentSupported: true,
moderation: {}
};
// Access the global app instance or find the video modal
// Looking at js/app.js, it might attach to window.app
// If not, we might need to find the modal instance another way.
// Let's assume window.app exists as is common in these setups,
// or we try to find the modal controller.
if (window.app && window.app.videoModal) {
await window.app.videoModal.open(mockVideo);
} else {
console.error("Could not find window.app.videoModal");
// Fallback: Try to find the modal element and manually unhide it
// to verify the toggle structure exists, though open() is better.
// Note: If window.app isn't exposed, we might struggle to invoke .open() directly
// without module injection.
}
}
""")
# Wait for the modal to appear
try:
page.wait_for_selector("#playerModal", state="visible", timeout=5000)
print("Modal opened successfully.")
except:
print("Modal did not open via app.videoModal. attempting manual check...")
# specific check for the toggle container
# The toggle container has attribute [data-source-toggle-container]
toggle_selector = "[data-source-toggle-container]"
# Expect it to be visible
try:
page.wait_for_selector(toggle_selector, state="visible", timeout=2000)
print("Source toggle container is visible!")
except:
print("Source toggle container NOT visible or not found.")
# If it's not visible, maybe the video didn't load right.
# Let's try to inspect the DOM to see if the element exists at all.
content = page.content()
if "data-source-toggle-container" in content:
print("Element exists in DOM but might be hidden.")
else:
print("Element does NOT exist in DOM.")
# Take a screenshot of the modal
# We can focus on the stats container where the toggle is
stats_selector = "[data-video-stats-container]"
try:
page.wait_for_selector(stats_selector, state="visible")
# Screenshot the whole page or just the modal
page.screenshot(path="/home/jules/verification/verification.png")
print("Screenshot saved.")
except Exception as e:
print(f"Could not take targeted screenshot: {e}")
page.screenshot(path="/home/jules/verification/verification_fallback.png")
browser.close()
if __name__ == "__main__":
verify_source_toggle()