1+ // Toast UI Helper
2+ function showToast ( message ) {
3+ let toast = document . getElementById ( "pip-toast" ) ;
4+ if ( ! toast ) {
5+ toast = document . createElement ( "div" ) ;
6+ toast . id = "pip-toast" ;
7+ toast . style . cssText = `
8+ position: fixed;
9+ bottom: 20px;
10+ left: 50%;
11+ transform: translateX(-50%);
12+ background: rgba(0, 0, 0, 0.8);
13+ color: white;
14+ padding: 8px 16px;
15+ border-radius: 20px;
16+ z-index: 999999;
17+ font-family: sans-serif;
18+ font-size: 14px;
19+ pointer-events: none;
20+ transition: opacity 0.3s;
21+ ` ;
22+ document . body . appendChild ( toast ) ;
23+ }
24+ toast . textContent = message ;
25+ toast . style . opacity = "1" ;
26+
27+ if ( window . toastTimeout ) clearTimeout ( window . toastTimeout ) ;
28+ window . toastTimeout = setTimeout ( ( ) => {
29+ toast . style . opacity = "0" ;
30+ } , 2000 ) ;
31+ }
32+
133function enablePiP ( video ) {
234 if ( video . dataset . pipEnabled ) return ;
335 video . dataset . pipEnabled = "true" ;
@@ -15,9 +47,123 @@ function enablePiP(video) {
1547 }
1648 } ) ;
1749
50+ // PiP Event Hooks
51+ video . addEventListener ( "enterpictureinpicture" , ( ) => {
52+ showToast ( "PiP Mode Enabled 📺" ) ;
53+ } ) ;
54+
55+ video . addEventListener ( "leavepictureinpicture" , ( ) => {
56+ showToast ( "PiP Mode Closed" ) ;
57+ } ) ;
58+
59+ // Document PiP Support (Advanced)
60+ video . dataset . docPipSupported = 'documentPictureInPicture' in window ;
61+ }
62+
63+ async function requestDocumentPiP ( video ) {
64+ if ( ! ( 'documentPictureInPicture' in window ) ) return false ;
65+
66+ try {
67+ const pipWindow = await window . documentPictureInPicture . requestWindow ( {
68+ width : video . videoWidth || 640 ,
69+ height : video . videoHeight || 360 ,
70+ } ) ;
71+
72+ // Style the PiP window
73+ pipWindow . document . body . style . margin = "0" ;
74+ pipWindow . document . body . style . background = "black" ;
75+ pipWindow . document . body . style . display = "flex" ;
76+ pipWindow . document . body . style . alignItems = "center" ;
77+ pipWindow . document . body . style . justifyContent = "center" ;
78+ pipWindow . document . body . style . overflow = "hidden" ;
79+
80+ // Create a container for video and custom controls
81+ const container = document . createElement ( "div" ) ;
82+ container . style . position = "relative" ;
83+ container . style . width = "100%" ;
84+ container . style . height = "100%" ;
85+
86+ // Move video to PiP
87+ const originalParent = video . parentElement ;
88+ const originalNextSibling = video . nextSibling ;
89+ container . appendChild ( video ) ;
90+ pipWindow . document . body . appendChild ( container ) ;
1891
92+ // Custom Overlay for Controls (Simplified)
93+ const overlay = document . createElement ( "div" ) ;
94+ overlay . style . cssText = "position:absolute;bottom:10px;left:50%;transform:translateX(-50%);display:none;gap:15px;background:rgba(0,0,0,0.6);padding:10px 20px;border-radius:30px;transition:opacity 0.2s;backdrop-filter:blur(5px);" ;
95+ overlay . innerHTML = `
96+ <button id="rewind" style="background:none;border:none;color:white;cursor:pointer;font-size:20px;">⏪</button>
97+ <button id="playPause" style="background:none;border:none;color:white;cursor:pointer;font-size:24px;">${ video . paused ? '▶️' : '⏸️' } </button>
98+ <button id="forward" style="background:none;border:none;color:white;cursor:pointer;font-size:20px;">⏩</button>
99+ ` ;
100+ container . appendChild ( overlay ) ;
101+
102+ container . onmouseenter = ( ) => overlay . style . display = "flex" ;
103+ container . onmouseleave = ( ) => overlay . style . display = "none" ;
104+
105+ // Logic for custom buttons
106+ overlay . querySelector ( "#playPause" ) . onclick = ( ) => {
107+ if ( video . paused ) video . play ( ) ; else video . pause ( ) ;
108+ overlay . querySelector ( "#playPause" ) . textContent = video . paused ? '▶️' : '⏸️' ;
109+ } ;
110+ overlay . querySelector ( "#rewind" ) . onclick = ( ) => video . currentTime -= 5 ;
111+ overlay . querySelector ( "#forward" ) . onclick = ( ) => video . currentTime += 5 ;
112+
113+ // Restore video on close
114+ pipWindow . addEventListener ( "pagehide" , ( ) => {
115+ if ( originalNextSibling ) {
116+ originalParent . insertBefore ( video , originalNextSibling ) ;
117+ } else {
118+ originalParent . appendChild ( video ) ;
119+ }
120+ } , { once : true } ) ;
121+
122+ return true ;
123+ } catch ( e ) {
124+ console . error ( "Document PiP failed:" , e ) ;
125+ return false ;
126+ }
19127}
20128
129+
130+ // Global Keyboard Shortcuts
131+ window . addEventListener ( "keydown" , ( e ) => {
132+ // Only trigger if a video exists on the page
133+ const video = document . querySelector ( "video" ) ;
134+ if ( ! video ) return ;
135+
136+ // Ignore if typing in an input/textarea
137+ if ( [ "INPUT" , "TEXTAREA" ] . includes ( document . activeElement . tagName ) || document . activeElement . isContentEditable ) {
138+ return ;
139+ }
140+
141+ switch ( e . key . toLowerCase ( ) ) {
142+ case "m" :
143+ video . muted = ! video . muted ;
144+ showToast ( video . muted ? "Muted 🔇" : "Unmuted 🔊" ) ;
145+ break ;
146+ case " " :
147+ e . preventDefault ( ) ; // Prevent scroll
148+ if ( video . paused ) {
149+ video . play ( ) ;
150+ showToast ( "Playing ▶️" ) ;
151+ } else {
152+ video . pause ( ) ;
153+ showToast ( "Paused ⏸️" ) ;
154+ }
155+ break ;
156+ case "arrowleft" :
157+ video . currentTime = Math . max ( 0 , video . currentTime - 5 ) ;
158+ showToast ( "Rewind 5s ⏪" ) ;
159+ break ;
160+ case "arrowright" :
161+ video . currentTime = Math . min ( video . duration , video . currentTime + 5 ) ;
162+ showToast ( "Forward 5s ⏩" ) ;
163+ break ;
164+ }
165+ } ) ;
166+
21167// Detect existing videos
22168document . querySelectorAll ( "video" ) . forEach ( enablePiP ) ;
23169
0 commit comments