Skip to content

Commit 48191f2

Browse files
committed
Using the Document Picture-in-Picture API (optional)
1 parent a388a0f commit 48191f2

File tree

3 files changed

+160
-22
lines changed

3 files changed

+160
-22
lines changed

features/picture-in-picture/data.json

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@
77
"url": "https://stio.studio/"
88
}
99
],
10-
"type": ["Website"],
11-
"tags": ["New", "Featured"],
10+
"type": [
11+
"Website"
12+
],
13+
"tags": [
14+
"New",
15+
"Featured"
16+
],
1217
"dynamic": true,
1318
"scripts": [
1419
{
1520
"file": "picture-in-picture.js",
1621
"runOn": "/projects/*"
1722
}
1823
],
19-
"components": [{
20-
"type": "info",
21-
"content": "Picture in Picture will not allow you to interact with the project. You must be on the project page to interact with it."
22-
}]
23-
}
24+
"resources": [{ "name": "popup-html", "path": "/popup.html" }],
25+
"options": [
26+
{
27+
"id": "interactivity-PiP",
28+
"name": "Make the project in picture popup interactive. (Experimental)",
29+
"type": 1
30+
}
31+
]
32+
}

features/picture-in-picture/picture-in-picture.js

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
export default async function ({ feature, console }) {
2-
const canvas = feature.traps.vm.renderer.canvas;
3-
4-
let video = document.createElement("video");
5-
// video.setAttribute("controls", "controls");
6-
video.setAttribute("autoplay", "autoplay");
7-
video.setAttribute("style", "width: 100%; height: 100%");
8-
// document.querySelector(".preview .inner").append(video);
9-
10-
video.srcObject = canvas.captureStream(30)
11-
122
await new Promise(async (resolve, reject) => {
133
(async () => {
144
const rem = await ScratchTools.waitForElement(".preview .inner .flex-row.action-buttons")
@@ -20,6 +10,7 @@ export default async function ({ feature, console }) {
2010
})();
2111
})
2212

13+
const canvas = feature.traps.vm.renderer.canvas;
2314
let openPopup = document.createElement("button");
2415

2516
ScratchTools.waitForElements(".preview .inner .flex-row.action-buttons", async function (row) {
@@ -45,12 +36,110 @@ export default async function ({ feature, console }) {
4536
})
4637
})
4738

48-
function popup() {
49-
try {
50-
video.requestPictureInPicture()
39+
let popup;
40+
41+
if (feature.settings.get("interactivity-PiP")) {
42+
if (!"documentPictureInPicture" in window) console.error("Picture in Picture not supported")
43+
44+
let pipWindow
45+
46+
let docPopup = document.createElement("div");
47+
docPopup.insertAdjacentHTML("afterbegin", await (await fetch(feature.self.getResource("popup-html"))).text())
48+
docPopup = docPopup.querySelector("div.popup-GUI")
49+
50+
let video = docPopup.querySelector("video");
51+
52+
const greenFlag = document.querySelector(".green-flag_green-flag_1kiAo")
53+
docPopup.querySelector(".popup-greenflag").addEventListener("click", () => {
54+
greenFlag.click()
55+
});
56+
const redFlag = document.querySelector(".stop-all_stop-all_1Y8P9")
57+
docPopup.querySelector(".popup-redflag").addEventListener("click", () => {
58+
redFlag.click()
59+
});
60+
61+
// video.addEventListener("mousedown", (old_event) => {
62+
function translateEvent_pointer(old_event) {
63+
// Calculate the canvas position relative to the viewport
64+
const a_rect = canvas.getBoundingClientRect();
65+
const b_rect = video.getBoundingClientRect();
66+
67+
// console.log(old_event)
68+
// Create a new event with the adjusted coordinates
69+
70+
let new_event = new old_event.constructor(old_event.type, {
71+
bubbles: old_event.bubbles,
72+
cancelable: old_event.cancelable,
73+
clientX: (old_event.clientX - b_rect.left) * (a_rect.width / b_rect.width) + a_rect.left,
74+
clientY: (old_event.clientY - b_rect.top) * (a_rect.height / b_rect.height) + a_rect.top,
75+
// Copy over other necessary properties from the old event
76+
screenX: (old_event.screenX - pipWindow.screenLeft + window.screenLeft - b_rect.left) * (a_rect.width / b_rect.width) + a_rect.left,
77+
screenY: (old_event.screenY - pipWindow.screenTop + window.screenTop - b_rect.top) * (a_rect.height / b_rect.height) + a_rect.top,
78+
layerX: old_event.layerX,
79+
layerY: old_event.layerY,
80+
button: old_event.button,
81+
buttons: old_event.buttons,
82+
relatedTarget: old_event.relatedTarget,
83+
altKey: old_event.altKey,
84+
ctrlKey: old_event.ctrlKey,
85+
shiftKey: old_event.shiftKey,
86+
metaKey: old_event.metaKey,
87+
movementX: old_event.movementX,
88+
movementY: old_event.movementY,
89+
});
90+
91+
// Dispatch the new event
92+
canvas.dispatchEvent(new_event);
93+
}
94+
video.addEventListener("mousedown", translateEvent_pointer)
95+
video.addEventListener("mouseup", translateEvent_pointer)
96+
video.addEventListener("mousemove", translateEvent_pointer)
97+
video.addEventListener("wheel", translateEvent_pointer)
98+
video.addEventListener("touchstart", translateEvent_pointer)
99+
video.addEventListener("touchend", translateEvent_pointer)
100+
video.addEventListener("touchmove", translateEvent_pointer)
101+
102+
function translateEvent_key(old_event) {
103+
let new_event = new KeyboardEvent(old_event.type, old_event)
104+
document.dispatchEvent(new_event);
51105
}
52-
catch {
53-
console.log("Picture in Picture not supported or failed to request")
106+
107+
let buttonClickedTimes = 0
108+
popup = async function () {
109+
if (buttonClickedTimes === 0) {
110+
video.srcObject = canvas.captureStream()
111+
buttonClickedTimes++
112+
}
113+
// Open a Picture-in-Picture window.
114+
pipWindow = await window.documentPictureInPicture.requestWindow({
115+
width: canvas.width,
116+
height: canvas.height + 20 + 6 * 2,
117+
});
118+
119+
// Move the player to the Picture-in-Picture window.
120+
pipWindow.document.body.append(docPopup);
121+
122+
pipWindow.document.addEventListener("keydown", translateEvent_key)
123+
pipWindow.document.addEventListener("keypress", translateEvent_key)
124+
pipWindow.document.addEventListener("keyup", translateEvent_key)
125+
}
126+
}
127+
else {
128+
let video = document.createElement("video");
129+
// video.setAttribute("controls", "controls");
130+
video.setAttribute("autoplay", "autoplay");
131+
video.setAttribute("style", "width: 100%; height: 100%");
132+
// document.querySelector(".preview .inner").append(video);
133+
134+
video.srcObject = canvas.captureStream()
135+
136+
popup = function () {
137+
try {
138+
video.requestPictureInPicture()
139+
}
140+
catch {
141+
console.log("Picture in Picture not supported or failed to request")
142+
}
54143
}
55144
}
56145
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<div class="popup-GUI">
2+
<style>
3+
.popup-GUI {
4+
width: 100%;
5+
height: 100%;
6+
display: flex;
7+
flex-direction: column;
8+
align-items: center;
9+
justify-content: center;
10+
}
11+
.popup-GUI video {
12+
max-width: 100%;
13+
max-height: 100%;
14+
aspect-ratio: 960/720;
15+
}
16+
.popup-GUI .popup-canvas {
17+
width: 100%;
18+
height: calc(100% - 20px);
19+
display: flex;
20+
justify-content: center;
21+
}
22+
.popup-GUI .popup-buttons {
23+
margin: auto;
24+
width: 100%;
25+
height: calc(20px + 6px * 2);
26+
}
27+
.popup-GUI .popup-buttons img {
28+
height: calc(100% - 6px * 2);
29+
aspect-ratio: 1/1;
30+
padding: 6px;
31+
}
32+
</style>
33+
<div class="popup-buttons">
34+
<img class="popup-greenflag" draggable="false" src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+" title="Go">
35+
<img class="popup-redflag" draggable="false" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNCAxNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTQgMTQ7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRUM1OTU5O3N0cm9rZTojQjg0ODQ4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9Cjwvc3R5bGU+Cjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNC4zLDAuNSA5LjcsMC41IDEzLjUsNC4zIDEzLjUsOS43IDkuNywxMy41IDQuMywxMy41IDAuNSw5LjcgMC41LDQuMyAiLz4KPC9zdmc+Cg==" title="Stop">
36+
</div>
37+
<div class="popup-canvas">
38+
<video autoplay></video>
39+
</div>
40+
</div>

0 commit comments

Comments
 (0)