Skip to content

Commit 4ac5fd9

Browse files
committed
Initial structure
1 parent 847694d commit 4ac5fd9

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import ContentFeature from '../content-feature.js';
2+
import { isBeingFramed } from '../utils.js';
3+
4+
export class DuckPlayerNative extends ContentFeature {
5+
init() {
6+
if (this.platform.name !== 'ios') return;
7+
8+
/**
9+
* This feature never operates in a frame
10+
*/
11+
if (isBeingFramed()) return;
12+
}
13+
}
14+
15+
export default DuckPlayerNative;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function getCurrentTime() {
2+
const video = document.querySelector('video');
3+
return video ? video.currentTime : 0;
4+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
(function() {
2+
// Initialize state if not exists
3+
if (!window._mediaControlState) {
4+
window._mediaControlState = {
5+
observer: null,
6+
userInitiated: false,
7+
originalPlay: HTMLMediaElement.prototype.play,
8+
originalLoad: HTMLMediaElement.prototype.load,
9+
isPaused: false
10+
};
11+
}
12+
const state = window._mediaControlState;
13+
14+
// Block playback handler
15+
const blockPlayback = function(event) {
16+
event.preventDefault();
17+
event.stopPropagation();
18+
return false;
19+
};
20+
21+
// The actual media control function
22+
function mediaControl(pause) {
23+
state.isPaused = pause;
24+
25+
if (pause) {
26+
// Capture play events at the earliest possible moment
27+
document.addEventListener('play', blockPlayback, true);
28+
document.addEventListener('playing', blockPlayback, true);
29+
30+
// Block HTML5 video/audio playback methods
31+
HTMLMediaElement.prototype.play = function() {
32+
this.pause();
33+
return Promise.reject(new Error('Playback blocked'));
34+
};
35+
36+
// Override load to ensure media starts paused
37+
HTMLMediaElement.prototype.load = function() {
38+
this.autoplay = false;
39+
this.pause();
40+
return state.originalLoad.apply(this, arguments);
41+
};
42+
43+
// Listen for user interactions that may lead to playback
44+
document.addEventListener('touchstart', () => {
45+
state.userInitiated = true;
46+
47+
// Remove the early blocking listeners
48+
document.removeEventListener('play', blockPlayback, true);
49+
document.removeEventListener('playing', blockPlayback, true);
50+
51+
// Reset HTMLMediaElement.prototype.play
52+
HTMLMediaElement.prototype.play = state.originalPlay;
53+
54+
// Unmute all media elements when user interacts
55+
document.querySelectorAll('audio, video').forEach(media => {
56+
media.muted = false;
57+
});
58+
59+
// Reset after a short delay
60+
setTimeout(() => {
61+
state.userInitiated = false;
62+
63+
// Re-add blocking if still in paused state
64+
if (state.isPaused) {
65+
document.addEventListener('play', blockPlayback, true);
66+
document.addEventListener('playing', blockPlayback, true);
67+
HTMLMediaElement.prototype.play = function() {
68+
this.pause();
69+
return Promise.reject(new Error('Playback blocked'));
70+
};
71+
}
72+
}, 500);
73+
}, true);
74+
75+
// Initial pause of all media
76+
document.querySelectorAll('audio, video').forEach(media => {
77+
media.pause();
78+
media.muted = true;
79+
media.autoplay = false;
80+
});
81+
82+
// Monitor DOM for newly added media elements
83+
if (state.observer) {
84+
state.observer.disconnect();
85+
}
86+
87+
state.observer = new MutationObserver(mutations => {
88+
mutations.forEach(mutation => {
89+
// Check for added nodes
90+
mutation.addedNodes.forEach(node => {
91+
if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {
92+
if (!state.userInitiated) {
93+
node.pause();
94+
node.muted = true;
95+
node.autoplay = false;
96+
}
97+
} else if (node.querySelectorAll) {
98+
node.querySelectorAll('audio, video').forEach(media => {
99+
if (!state.userInitiated) {
100+
media.pause();
101+
media.muted = true;
102+
media.autoplay = false;
103+
}
104+
});
105+
}
106+
});
107+
});
108+
});
109+
110+
state.observer.observe(document.documentElement || document.body, {
111+
childList: true,
112+
subtree: true,
113+
attributes: true,
114+
attributeFilter: ['autoplay', 'src', 'playing']
115+
});
116+
} else {
117+
// Restore original methods
118+
HTMLMediaElement.prototype.play = state.originalPlay;
119+
HTMLMediaElement.prototype.load = state.originalLoad;
120+
121+
// Remove listeners
122+
document.removeEventListener('play', blockPlayback, true);
123+
document.removeEventListener('playing', blockPlayback, true);
124+
125+
// Clean up observer
126+
if (state.observer) {
127+
state.observer.disconnect();
128+
state.observer = null;
129+
}
130+
131+
// Unmute all media
132+
document.querySelectorAll('audio, video').forEach(media => {
133+
media.muted = false;
134+
});
135+
}
136+
}
137+
138+
// Export function
139+
window.mediaControl = mediaControl;
140+
})();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function muteAudio(mute) {
2+
document.querySelectorAll('audio, video').forEach(media => {
3+
media.muted = mute;
4+
});
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
window.dispatchEvent(
2+
new CustomEvent('ddg-serp-yt-response', {
3+
detail: {
4+
kind: 'initialSetup',
5+
data: {
6+
privatePlayerMode: { enabled: {} },
7+
overlayInteracted: false,
8+
}
9+
},
10+
composed: true,
11+
bubbles: true,
12+
}),
13+
)

0 commit comments

Comments
 (0)