Skip to content

Commit

Permalink
feat: spawn custom video player process #415
Browse files Browse the repository at this point in the history
  • Loading branch information
ThaUnknown committed Mar 22, 2024
1 parent 5820918 commit 17498e8
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 32 deletions.
3 changes: 2 additions & 1 deletion capacitor/src/support.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export const SUPPORTS = {
torrentPath: false,
torrentPersist: false,
keybinds: false,
isAndroid: true
isAndroid: true,
externalPlayer: false
}
3 changes: 2 additions & 1 deletion common/modules/support.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export const SUPPORTS = {
torrentPersist: true,
keybinds: true,
extensions: true,
isAndroid: false
isAndroid: false,
externalPlayer: true
}
34 changes: 29 additions & 5 deletions common/modules/webtorrent.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { spawn } from 'node:child_process'
import WebTorrent from 'webtorrent'
import HTTPTracker from 'bittorrent-tracker/lib/client/http-tracker.js'
import { hex2bin, arr2hex, text2arr } from 'uint8-util'
Expand Down Expand Up @@ -33,6 +34,10 @@ try {
export default class TorrentClient extends WebTorrent {
static excludedErrorMessages = ['WebSocket', 'User-Initiated Abort, reason=', 'Connection failed.']

player = ''
/** @type {ReturnType<spawn>} */
playerProcess = null

constructor (ipc, storageQuota, serverMode, settingOverrides = {}, controller) {
const settings = { ...defaults, ...storedSettings, ...settingOverrides }
super({
Expand All @@ -55,6 +60,9 @@ export default class TorrentClient extends WebTorrent {
})
ipc.on('destroy', this.destroy.bind(this))
})
ipc.on('player', (event, data) => {
this.player = data
})
this.settings = settings

this.serverMode = serverMode
Expand Down Expand Up @@ -219,18 +227,34 @@ export default class TorrentClient extends WebTorrent {
switch (data.type) {
case 'current': {
if (data.data) {
const torrent = await this.get(data.data.infoHash)
const found = torrent?.files.find(file => file.path === data.data.path)
const torrent = await this.get(data.data.current.infoHash)
const found = torrent?.files.find(file => file.path === data.data.current.path)
if (!found) return
if (this.playerProcess) {
this.playerProcess.kill()
this.playerProcess = null
}
if (this.current) {
this.current.removeAllListeners('stream')
}
this.parser?.destroy()
found.select()
this.current = found
this.parser = new Parser(this, found)
this.findSubtitleFiles(found)
this.findFontFiles(found)
if (data.data.external && this.player) {
this.playerProcess = spawn(this.player, ['http://localhost:' + this.server.address().port + found.streamURL])
this.playerProcess.stdout.on('data', () => {})
const startTime = Date.now()
this.playerProcess.once('close', () => {
this.playerProcess = null
const seconds = (Date.now() - startTime) / 1000
console.log(seconds)
this.dispatch('externalWatched', seconds)
})
} else {
this.parser = new Parser(this, found)
this.findSubtitleFiles(found)
this.findFontFiles(found)
}
}
break
}
Expand Down
44 changes: 31 additions & 13 deletions common/views/Player/Player.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@
}
$: loadDeband($settings.playerDeband, video)
let watchedListener
async function handleCurrent (file) {
if (file) {
if (thumbnailData.video?.src) URL.revokeObjectURL(video?.src)
Expand All @@ -168,14 +170,26 @@
chapters = []
currentSkippable = null
completed = false
if (subs) subs.destroy()
if (subs) {
subs.destroy()
subs = null
}
current = file
emit('current', current)
src = file.url
client.send('current', file)
subs = new Subtitles(video, files, current, handleHeaders)
video.load()
await loadAnimeProgress()
client.send('current', { current: file, external: settings.value.enableExternal })
if (!settings.value.enableExternal) {
src = file.url
subs = new Subtitles(video, files, current, handleHeaders)
video.load()
await loadAnimeProgress()
} else if (current.media?.media?.duration) {
const duration = current.media?.media?.duration
client.removeEventListener('externalWatched', watchedListener)
watchedListener = ({ detail }) => {
checkCompletionByTime(detail, duration)
}
client.addEventListener('externalWatched', watchedListener)
}
}
}
Expand Down Expand Up @@ -882,13 +896,17 @@
let completed = false
function checkCompletion () {
if (!completed && $settings.playerAutocomplete) {
const fromend = Math.max(180, safeduration / 10)
if (safeduration && currentTime && video?.readyState && safeduration - fromend < currentTime) {
if (media?.media?.episodes || media?.media?.nextAiringEpisode?.episode) {
if (media.media.episodes || media.media.nextAiringEpisode?.episode > media.episode) {
completed = true
anilistClient.alEntry(media)
}
checkCompletionByTime(currentTime, safeduration)
}
}
function checkCompletionByTime (time, duration) {
const fromend = Math.max(180, duration / 10)
if (time && time && video?.readyState && time - fromend < time) {
if (media?.media?.episodes || media?.media?.nextAiringEpisode?.episode) {
if (media.media.episodes || media.media.nextAiringEpisode?.episode > media.episode) {
completed = true
anilistClient.alEntry(media)
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions common/views/Settings/PlayerSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import { toast } from 'svelte-sonner'
import FontSelect from 'simple-font-select'
import SettingCard from './SettingCard.svelte'
import { SUPPORTS } from '@/modules/support.js'
import { click } from '@/modules/click.js'
import IPC from '@/modules/ipc.js'
export let settings
async function changeFont ({ detail }) {
Expand All @@ -21,6 +24,9 @@
})
}
}
function handleExecutable () {
IPC.emit('player')
}
</script>

{#if ('queryLocalFonts' in self)}
Expand Down Expand Up @@ -131,3 +137,22 @@
<label for='player-deband'>{settings.playerDeband ? 'On' : 'Off'}</label>
</div>
</SettingCard>

{#if SUPPORTS.externalPlayer}
<h4 class='mb-10 font-weight-bold'>External Player Settings</h4>
<SettingCard title='Enable External Player' description='Tells Miru to open a custom user-defined video player to play video, instead of using the built-in one.'>
<div class='custom-switch'>
<input type='checkbox' id='player-external-enabled' bind:checked={settings.enableExternal} />
<label for='player-external-enabled'>{settings.enableExternal ? 'On' : 'Off'}</label>
</div>
</SettingCard>
<SettingCard title='External Video Player' description='Executable for an external video player. Make sure the player supports HTTP sources.'>
<div
class='input-group w-300 mw-full'>
<div class='input-group-prepend'>
<button type='button' use:click={handleExecutable} class='btn btn-primary input-group-append'>Select Executable</button>
</div>
<input type='url' class='form-control bg-dark' readonly value={settings.playerPath} />
</div>
</SettingCard>
{/if}
6 changes: 6 additions & 0 deletions common/views/Settings/Settings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
$settings.torrentPath = data
}
function playerListener (data) {
$settings.playerPath = data
}
function loginButton () {
if (anilistClient.userID) {
$logout = true
Expand All @@ -95,9 +99,11 @@
}
onDestroy(() => {
IPC.off('path', pathListener)
IPC.off('player', playerListener)
})
$: IPC.emit('show-discord-status', $settings.showDetailsInRPC)
IPC.on('path', pathListener)
IPC.on('player', playerListener)
</script>

<Tabs>
Expand Down
25 changes: 25 additions & 0 deletions electron/src/main/dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { basename, extname } from 'node:path'
import { ipcMain, dialog } from 'electron'
import store from './store.js'

export default class Dialog {
/**
* @param {import('electron').BrowserWindow} torrentWindow
*/
constructor (torrentWindow) {
ipcMain.on('player', async ({ sender }) => {
const { filePaths, canceled } = await dialog.showOpenDialog({
title: 'Select video player executable',
properties: ['openFile']
})
if (canceled) return
if (filePaths.length) {
const path = filePaths[0]

store.set('player', path)
torrentWindow.webContents.send('player', path)
sender.send('player', basename(path, extname(path)))
}
})
}
}
2 changes: 1 addition & 1 deletion electron/src/main/discord.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Client } from 'discord-rpc'
import { ipcMain } from 'electron'
import { debounce } from '@/modules/util.js'

export default class {
export default class Discord {
defaultStatus = {
activity: {
timestamps: { start: Date.now() },
Expand Down
4 changes: 4 additions & 0 deletions electron/src/main/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Discord from './discord.js'
import Updater from './updater.js'
import Protocol from './protocol.js'
import { development } from './util.js'
import Dialog from './dialog.js'
import store from './store.js'

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
Expand Down Expand Up @@ -44,6 +46,7 @@ function createWindow () {
new Discord(mainWindow)
new Protocol(mainWindow)
new Updater(mainWindow)
new Dialog(webtorrentWindow)
mainWindow.setMenuBarVisibility(false)

mainWindow.webContents.session.webRequest.onHeadersReceived(({ responseHeaders }, fn) => {
Expand Down Expand Up @@ -107,6 +110,7 @@ function createWindow () {
const { port1, port2 } = new MessageChannelMain()
await torrentLoad
webtorrentWindow.webContents.postMessage('port', null, [port1])
webtorrentWindow.webContents.postMessage('player', store.get('player'))
sender.postMessage('port', null, [port2])
})
}
Expand Down
2 changes: 1 addition & 1 deletion electron/src/main/protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if (process.defaultApp) {
app.setAsDefaultProtocolClient('miru')
}

export default class {
export default class Protocol {
// schema: miru://key/value
protocolMap = {
auth: token => this.sendToken(token),
Expand Down
2 changes: 1 addition & 1 deletion electron/src/main/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ function parseDataFile (filePath, defaults) {
}
}

export default new Store('settings', { angle: 'default' })
export default new Store('settings', { angle: 'default', player: '' })
2 changes: 1 addition & 1 deletion electron/src/main/updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ipcMain.on('update', () => {
})

autoUpdater.checkForUpdatesAndNotify()
export default class {
export default class Updater {
/**
* @param {import('electron').BrowserWindow} window
*/
Expand Down
15 changes: 7 additions & 8 deletions electron/src/main/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,9 @@ ipcMain.on('open', (event, url) => {

ipcMain.on('doh', (event, dns) => {
try {
const url = new URL(dns)

app.configureHostResolver({
secureDnsMode: 'secure',
secureDnsServers: [url.toString()]
secureDnsServers: ['' + new URL(dns)]
})
} catch (e) {}
})
Expand All @@ -50,10 +48,11 @@ ipcMain.on('close', () => {
app.quit()
})

ipcMain.on('dialog', async (event, data) => {
const { filePaths } = await dialog.showOpenDialog({
ipcMain.on('dialog', async ({ sender }) => {
const { filePaths, canceled } = await dialog.showOpenDialog({
properties: ['openDirectory']
})
if (canceled) return
if (filePaths.length) {
let path = filePaths[0]
if (!(path.endsWith('\\') || path.endsWith('/'))) {
Expand All @@ -63,12 +62,12 @@ ipcMain.on('dialog', async (event, data) => {
path += '/'
}
}
event.sender.send('path', path)
sender.send('path', path)
}
})

ipcMain.on('version', (event) => {
event.sender.send('version', app.getVersion()) // fucking stupid
ipcMain.on('version', ({ sender }) => {
sender.send('version', app.getVersion()) // fucking stupid
})

app.setJumpList?.([
Expand Down

0 comments on commit 17498e8

Please sign in to comment.