Skip to content

Commit 93e56e3

Browse files
authored
Added Pop-Out/PiP Player
- merge commits from dev/latest which add a new feature to open movies/episodes in a popout window - the popout window is always on top and shows the source site's player UI while redirecting the real player to about:blank and showing a button to close the popout there - Implements #34 into the main branch
2 parents f6cb648 + f04b15f commit 93e56e3

6 files changed

Lines changed: 636 additions & 31 deletions

File tree

index.js

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// ── Streambert main process entry point ───────────────────────────────────────
1+
// -- Streambert main process entry point ---------------------------------------
22
// Responsible for: window creation, session setup, ad-blocking, scheduled
33
// backup trigger, and app lifecycle. All heavy IPC logic lives in src/ipc/.
44

@@ -12,7 +12,7 @@ const {
1212
} = require("electron");
1313
const path = require("path");
1414

15-
// ── RAM / performance flags ───────────────────────────────────────────────────
15+
// -- RAM / performance flags ---------------------------------------------------
1616
app.commandLine.appendSwitch(
1717
"js-flags",
1818
"--max-old-space-size=256 --expose-gc",
@@ -30,20 +30,20 @@ app.commandLine.appendSwitch("enable-features", "NetworkServiceInProcess2");
3030
app.commandLine.appendSwitch("disk-cache-size", String(80 * 1024 * 1024));
3131
app.commandLine.appendSwitch("renderer-process-limit", "3");
3232

33-
// ── Startup benchmark ─────────────────────────────────────────────────────────
33+
// -- Startup benchmark ---------------------------------------------------------
3434
const _t0 = Date.now();
3535
const _bench = (label) =>
3636
console.log(`[boot] ${label}: +${Date.now() - _t0}ms`);
3737

38-
// ── Sub-modules ───────────────────────────────────────────────────────────────
38+
// -- Sub-modules ---------------------------------------------------------------
3939
const blockStats = require("./src/ipc/blockStats");
4040
const storageIpc = require("./src/ipc/storage");
4141
const downloadsIpc = require("./src/ipc/downloads");
4242
const subtitlesIpc = require("./src/ipc/subtitles");
4343
const allmangaIpc = require("./src/ipc/allmanga");
4444
const playerIpc = require("./src/ipc/player");
4545

46-
// ── Ad/tracker block list ─────────────────────────────────────────────────────
46+
// -- Ad/tracker block list -----------------------------------------------------
4747
const BLOCKED_HOSTS = [
4848
"*://www.google-analytics.com/*",
4949
"*://analytics.google.com/*",
@@ -98,11 +98,12 @@ const BLOCKED_HOSTS = [
9898
"*://tmstr4.neonhorizonworkshops.com/*",
9999
];
100100

101-
// ── Module-level state ────────────────────────────────────────────────────────
101+
// -- Module-level state --------------------------------------------------------
102102
let mainWindow = null;
103103
const getMainWindow = () => mainWindow;
104104

105105
const playerWcIds = new Set();
106+
let sessionsConfigured = false;
106107

107108
function setupSession(playerSession, trailerSession) {
108109
const stripHeaders = (details, callback) => {
@@ -238,10 +239,9 @@ function createWindow() {
238239
},
239240
);
240241

241-
// ── Lazy session setup ────────────────────────────────────────────────────
242-
// Player/trailer sessions are only configured on the first webview attach
243-
// (i.e. when the user actually opens a movie/trailer), not at startup.
244-
let sessionsConfigured = false;
242+
// -- Lazy session setup ----------------------------------------------------
243+
// Player/trailer sessions are configured on the first webview attach or
244+
// when the pop-out window opens, whichever comes first.
245245

246246
// Block popups from webviews, intercept fullscreen, lazy-init sessions
247247
mainWindow.webContents.on("did-attach-webview", (_, wc) => {
@@ -307,7 +307,7 @@ function createWindow() {
307307
});
308308
}
309309

310-
// ── Register all IPC modules ──────────────────────────────────────────────────
310+
// -- Register all IPC modules --------------------------------------------------
311311
storageIpc.register();
312312
downloadsIpc.register(getMainWindow);
313313
subtitlesIpc.register({
@@ -323,7 +323,7 @@ blockStats.init(getMainWindow);
323323
// get-block-stats lives with its data
324324
ipcMain.handle("get-block-stats", () => blockStats.getBlockStats());
325325

326-
// ── Player memory cleanup ─────────────────────────────────────────────
326+
// -- Player memory cleanup ---------------------------------------------
327327
// Called by MoviePage / TVPage on component unmount.
328328
// Destroys the player webview WebContents by tracked ID, then flushes caches and GCs.
329329
ipcMain.on("player-stopped", () => {
@@ -360,7 +360,7 @@ ipcMain.on("player-stopped", () => {
360360
}
361361
});
362362

363-
// ── Wyzie API Key Redemption Window ──────────────────────────────────────────
363+
// -- Wyzie API Key Redemption Window ------------------------------------------
364364
// Opens https://sub.wyzie.io/redeem in a child BrowserWindow, watches the DOM
365365
// for the api-key-display element, extracts the key, and sends it back.
366366
ipcMain.handle("wyzie-open-redeem", async () => {
@@ -437,7 +437,7 @@ ipcMain.handle("wyzie-open-redeem", async () => {
437437
});
438438
});
439439

440-
// ── Wyzie API Key Validation ──────────────────────────────────────────────────
440+
// -- Wyzie API Key Validation --------------------------------------------------
441441
ipcMain.handle("wyzie-validate-key", async (_, key) => {
442442
try {
443443
const controller = new AbortController();
@@ -455,7 +455,7 @@ ipcMain.handle("wyzie-validate-key", async (_, key) => {
455455
}
456456
});
457457

458-
// ── Desktop notifications ─────────────────────────────────────────────────────
458+
// -- Desktop notifications -----------------------------------------------------
459459
// Called from the renderer whenever it wants a native OS notification.
460460
ipcMain.handle(
461461
"show-notification",
@@ -472,7 +472,111 @@ ipcMain.handle(
472472
},
473473
);
474474

475-
// ── Single-instance lock ──────────────────────────────────────────────────────
475+
// -- Picture-in-Picture / Pop-Out window --------------------------------------
476+
// Opens the player URL in a small always-on-top BrowserWindow (full site UI,
477+
// with subtitles and controls). The Main Window closes the stream to avoid duplication.
478+
let pipWindow = null;
479+
const getPipWindow = () => pipWindow;
480+
481+
ipcMain.handle("open-pip-window", (_, { url, title }) => {
482+
if (!url || url === "about:blank") return { ok: false, reason: "no-url" };
483+
484+
// Guarantee tracker/ad blocking is active in persist:player before any load
485+
if (!sessionsConfigured) {
486+
sessionsConfigured = true;
487+
const playerSession = session.fromPartition("persist:player");
488+
const trailerSession = session.fromPartition("persist:trailer");
489+
setupSession(playerSession, trailerSession);
490+
}
491+
492+
if (pipWindow && !pipWindow.isDestroyed()) {
493+
pipWindow.loadURL(url);
494+
pipWindow.focus();
495+
return { ok: true };
496+
}
497+
498+
pipWindow = new BrowserWindow({
499+
width: 640,
500+
height: 360,
501+
minWidth: 320,
502+
minHeight: 180,
503+
alwaysOnTop: true,
504+
title: title ? `${title} - Pop-out` : "Pop-out Player",
505+
backgroundColor: "#000000",
506+
// Same custom title bar as the main window
507+
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "hidden",
508+
frame: process.platform !== "win32",
509+
webPreferences: {
510+
partition: "persist:player",
511+
nodeIntegration: false,
512+
contextIsolation: true,
513+
// Injects the custom title bar and wires window-control IPC
514+
preload: path.join(__dirname, "popout-preload.js"),
515+
},
516+
});
517+
518+
// Block all popup windows from the streaming site and any nested frames
519+
pipWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
520+
521+
// If the site uses <webview> elements (unlikely but safe), block there too
522+
pipWindow.webContents.on("did-attach-webview", (_, wc) => {
523+
wc.setWindowOpenHandler(() => ({ action: "deny" }));
524+
});
525+
526+
pipWindow.loadURL(url);
527+
528+
// Push maximize state into the popout renderer so the title bar icon updates
529+
pipWindow.on("maximize", () => {
530+
if (!pipWindow.isDestroyed())
531+
pipWindow.webContents.send("popout-window-maximized", true);
532+
});
533+
pipWindow.on("unmaximize", () => {
534+
if (!pipWindow.isDestroyed())
535+
pipWindow.webContents.send("popout-window-maximized", false);
536+
});
537+
538+
const notifyMain = (channel) => {
539+
const mw = getMainWindow();
540+
if (mw && !mw.isDestroyed()) mw.webContents.send(channel);
541+
};
542+
543+
pipWindow.on("closed", () => {
544+
pipWindow = null;
545+
notifyMain("pip-window-closed");
546+
});
547+
548+
notifyMain("pip-window-opened");
549+
return { ok: true };
550+
});
551+
552+
ipcMain.handle("close-pip-window", () => {
553+
if (pipWindow && !pipWindow.isDestroyed()) pipWindow.close();
554+
});
555+
556+
ipcMain.handle("get-pip-webcontents-id", () => {
557+
if (pipWindow && !pipWindow.isDestroyed()) return pipWindow.webContents.id;
558+
return null;
559+
});
560+
561+
// -- Popout window controls (used by popout-preload.js title bar buttons) -----
562+
ipcMain.handle("popout-window-minimize", () => {
563+
if (pipWindow && !pipWindow.isDestroyed()) pipWindow.minimize();
564+
});
565+
ipcMain.handle("popout-window-toggle-maximize", () => {
566+
if (!pipWindow || pipWindow.isDestroyed()) return;
567+
if (pipWindow.isMaximized()) pipWindow.unmaximize();
568+
else pipWindow.maximize();
569+
});
570+
ipcMain.handle("popout-window-close", () => {
571+
if (pipWindow && !pipWindow.isDestroyed()) pipWindow.close();
572+
});
573+
ipcMain.handle("popout-window-is-maximized", () => {
574+
return pipWindow && !pipWindow.isDestroyed()
575+
? pipWindow.isMaximized()
576+
: false;
577+
});
578+
579+
// -- Single-instance lock ------------------------------------------------------
476580
const gotTheLock = app.requestSingleInstanceLock();
477581

478582
if (!gotTheLock) {

0 commit comments

Comments
 (0)