Skip to content

Commit

Permalink
✨ Optimizations
Browse files Browse the repository at this point in the history
- Lessons.church updates
- Fixed main screen showing in output window
- Added main loading
- Fixed Windows maximized state
- Fixed thumbnails not generating if video is not ready
- Fixed some thumbnails getting corrupted
- Fixed slide title zoom
- Slide background loop indicator
  • Loading branch information
vassbo committed Jun 27, 2024
1 parent 51597f8 commit 47fc52d
Show file tree
Hide file tree
Showing 24 changed files with 201 additions and 242 deletions.
146 changes: 9 additions & 137 deletions src/electron/data/thumbnails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { isProd, toApp } from ".."
import { MAIN } from "../../types/Channels"
import { doesPathExist } from "../utils/files"
import { waitUntilValueIsDefined } from "../utils/helpers"
import { defaultSettings } from "./defaults"

export function getThumbnail(data: any) {
let output = createThumbnail(data.input, data.size || 500)
Expand Down Expand Up @@ -90,153 +89,26 @@ function hashCode(str: string) {
return "a" + hash.toString()
}

///// CUSTOM WINDOW /////

// const JS_IMAGE = 'document.querySelector("img")'
// const JS_IMAGE_LOADED = JS_IMAGE + "?.complete"
// const JS_IMAGE_WIDTH = JS_IMAGE + ".naturalWidth"
// const JS_IMAGE_HEIGHT = JS_IMAGE + ".naturalHeight"

// const JS_VIDEO = 'document.querySelector("video")'
// const JS_VIDEO_READY = JS_VIDEO + "?.readyState"
// const JS_GET_DURATION = JS_VIDEO + ".duration"
// const JS_PAUSE_VIDEO = JS_VIDEO + ".pause()"
// const JS_REMOVE_CONTROLS = JS_VIDEO + '.removeAttribute("controls")'
// const JS_SET_TIME = JS_VIDEO + ".currentTime="
// const JS_VIDEO_WIDTH = JS_VIDEO + ".videoWidth"
// const JS_VIDEO_HEIGHT = JS_VIDEO + ".videoHeight"

// let captureWindow: BrowserWindow | null = null
// async function createCaptureWindow(data: any) {
// if (!captureWindow) captureWindow = new BrowserWindow(captureOptions)

// captureWindow?.loadFile(data.input)

// // wait until loaded
// let contentLoaded: boolean = false
// captureWindow?.once("ready-to-show", () => {
// contentLoaded = true
// })
// await waitUntilValueIsDefined(() => contentLoaded, 20, 1000)
// if (!contentLoaded) return exit()

// let extension = getExtension(data.input)
// if (customImageCapture.includes(extension)) return checkImage()

// let videoLoaded: any = await waitUntilValueIsDefined(checkIfVideoHasLoaded, 20, 2000)
// if (!videoLoaded) {
// // probably unsupported codec
// if (extension === "mp4") return exit()
// return checkImage()
// }

// let videoDuration = await captureWindow?.webContents.executeJavaScript(JS_GET_DURATION)
// if (!videoDuration) return exit()

// let seekTo = Math.floor(videoDuration) * (data.seek ?? 0.5)

// captureWindow?.webContents.executeJavaScript(JS_PAUSE_VIDEO)
// captureWindow?.webContents.executeJavaScript(JS_REMOVE_CONTROLS)
// captureWindow?.webContents.executeJavaScript(JS_SET_TIME + seekTo)

// // check if video seek has loaded properly
// await waitUntilValueIsDefined(checkIfVideoHasLoaded, 20)

// let videoWidth = await captureWindow?.webContents.executeJavaScript(JS_VIDEO_WIDTH)
// let videoHeight = await captureWindow?.webContents.executeJavaScript(JS_VIDEO_HEIGHT)

// await setWindowSize(videoWidth, videoHeight)

// captureContent()

// async function checkImage() {
// let hasLoaded: any = await waitUntilValueIsDefined(checkIfImageHasLoaded, 20, 2000)
// if (!hasLoaded) return exit()

// let imageWidth = await captureWindow?.webContents.executeJavaScript(JS_IMAGE_WIDTH)
// let imageHeight = await captureWindow?.webContents.executeJavaScript(JS_IMAGE_HEIGHT)

// await setWindowSize(imageWidth, imageHeight)

// captureContent()
// return
// }

// async function setWindowSize(contentWidth: number, contentHeight: number) {
// if (!contentWidth) contentWidth = 1920
// if (!contentHeight) contentHeight = 1080

// const ratio = contentWidth / contentHeight
// let width = data.size?.width
// let height = data.size?.height
// if (!width) width = height ? Math.floor(height * ratio) : contentWidth
// if (!height) height = data.size?.width ? Math.floor(width / ratio) : contentHeight

// captureWindow?.setSize(width, height)

// // wait for window and content to get resized
// await wait(50)
// }

// async function checkIfImageHasLoaded() {
// let imageComplete = await captureWindow?.webContents.executeJavaScript(JS_IMAGE_LOADED)
// return imageComplete
// }

// async function checkIfVideoHasLoaded() {
// let readyState = await captureWindow?.webContents.executeJavaScript(JS_VIDEO_READY)
// return readyState === 4
// }

// async function captureContent() {
// let image = await captureWindow?.webContents.capturePage()
// if (!image) return exit()

// removeCaptureWindow()
// saveToDisk(data.output, image)
// }

// function exit() {
// generationFinished()
// }
// }
// function removeCaptureWindow() {
// if (!captureWindow) return
// if (captureWindow.isDestroyed()) {
// captureWindow = null
// return
// }

// captureWindow.on("closed", () => (captureWindow = null))
// captureWindow.destroy()
// }

///// GENERATE /////

interface Config {
seek?: number // 0-1
// format?: "jpg" | "jpeg" | "png"
// quality?: number // 0-100
}
const customImageCapture = ["gif", "webp"]
// const customImageCapture = ["gif", "webp"]
async function generate(input: string, output: string, size: string, config: Config = {}) {
if (!input || !output) {
generationFinished()
return
}

const parsedSize = parseSize(size)
let extension = getExtension(input)

// // mov files can't be opened directly, but can be played as video elem
// if (extension === "mov") return captureWithCanvas({ input, output, size: parsedSize, config })
// // capture images directly from electron nativeImage (fastest)
// if (!customImageCapture.includes(extension) && defaultSettings.imageExtensions.includes(extension)) return captureImage(input, output, parsedSize)
// // capture videos in custom window (to reduce load on main window)
// createCaptureWindow({ input, output, size: parsedSize, config }) // WIP this would many times create a save dialog window in some cases
// let extension = getExtension(input)

// capture images directly from electron nativeImage (fastest)
if (!customImageCapture.includes(extension) && defaultSettings.imageExtensions.includes(extension)) return captureImage(input, output, parsedSize)
// WIP this was unreliable
// if (!customImageCapture.includes(extension) && defaultSettings.imageExtensions.includes(extension)) return captureImage(input, output, parsedSize)

// capture other media with canvas in main window
await captureWithCanvas({ input, output, size: parsedSize, extension: getExtension(input), config })
Expand Down Expand Up @@ -265,12 +137,12 @@ export function saveImage(data: any) {
}

// https://www.electronjs.org/docs/latest/api/native-image
function captureImage(input: string, output: string, size: ResizeOptions) {
let outputImage = nativeImage.createFromPath(input)
outputImage = outputImage.resize(size)
// function captureImage(input: string, output: string, size: ResizeOptions) {
// let outputImage = nativeImage.createFromPath(input)
// outputImage = outputImage.resize(size)

saveToDisk(output, outputImage)
}
// saveToDisk(output, outputImage)
// }

function getExtension(filePath: string) {
return path.extname(filePath).slice(1).toLowerCase()
Expand Down
2 changes: 1 addition & 1 deletion src/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function mainWindowLoaded() {

initialize()

if (config.get("maximized")) mainWindow!.maximize()
if (config.get("maximized")) maximizeMain()
mainWindow?.show()
loadingWindow?.close()

Expand Down
2 changes: 1 addition & 1 deletion src/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ValidChannels } from "../types/Channels"
// wait to log messages until after intial load is done
let appLoaded: boolean = false
const LOG_MESSAGES: boolean = process.env.NODE_ENV !== "production"
const filteredChannels: any[] = ["AUDIO_MAIN", "VIZUALISER_DATA", "STREAM", "PREVIEW", "REQUEST_STREAM", "MAIN_TIME"]
const filteredChannels: any[] = ["AUDIO_MAIN", "VIZUALISER_DATA", "STREAM", "PREVIEW", "REQUEST_STREAM", "MAIN_TIME", "GET_THUMBNAIL"]

let storedReceivers: any = {}

Expand Down
26 changes: 10 additions & 16 deletions src/frontend/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import ContextMenu from "./components/context/ContextMenu.svelte"
import Pdf from "./components/export/Pdf.svelte"
import { startEventTimer, startTimer } from "./components/helpers/timerTick"
import Loader from "./components/main/Loader.svelte"
import MenuBar from "./components/main/MenuBar.svelte"
import Popup from "./components/main/Popup.svelte"
import Recorder from "./components/main/Recorder.svelte"
import Toast from "./components/main/Toast.svelte"
import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, os, outputDisplay } from "./stores"
import Center from "./components/system/Center.svelte"
import { activeTimers, autosave, closeAd, currentWindow, disabledServers, events, loaded, os, outputDisplay } from "./stores"
import { focusArea, logerror, startAutosave, toggleRemoteStream } from "./utils/common"
import { keydown } from "./utils/shortcuts"
import { startup } from "./utils/startup"
Expand All @@ -29,7 +31,7 @@
$: if ($autosave) startAutosave()
// stream to OutputShow
$: if (!$currentWindow && ($disabledServers.output_stream !== "" || !$outputDisplay)) toggleRemoteStream()
$: if (($loaded && $disabledServers.output_stream !== "") || !$outputDisplay) setTimeout(toggleRemoteStream, 1000)
// close youtube ad
$: if ($closeAd) setTimeout(() => closeAd.set(false), 10)
Expand All @@ -40,6 +42,7 @@
{#if $currentWindow === "pdf"}
<Pdf />
{:else}
<!-- "isWindows" is only set in main window -->
{#if isWindows}
<MenuBar />
{/if}
Expand All @@ -49,15 +52,16 @@

{#if $currentWindow === "output"}
<MainOutput />
{:else}
<!-- WIP black window before output is loaded (don't show app screen when creating output windows) -->
<!-- {#if !$loaded}<div class="black" />{/if} -->

{:else if $loaded}
<Popup />
<Toast />
<Recorder />

<MainLayout />
{:else}
<Center>
<Loader size={2} />
</Center>
{/if}
</main>
{/if}
Expand All @@ -73,14 +77,4 @@
.closeAd {
height: 1px;
}
/* .black {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
z-index: 500;
} */
</style>
2 changes: 1 addition & 1 deletion src/frontend/components/draw/Slide.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@

<!-- using capture is too slow -->
<div class="parent" bind:this={parent} bind:offsetWidth={width} bind:offsetHeight={height}>
<div style="width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;" on:mousedown={onMouseMove} on:wheel={wheel}>
<div style="width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;" on:mousedown={onMouseMove} on:wheel|passive={wheel}>
<!-- TODO: draw get video time! -->
<Output {outputId} style={getStyleResolution(resolution, width, height, "fit")} bind:ratio mirror />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/components/helpers/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function sortByTime(a, b) {

// sort objects in array by name
export function sortByName(arr: any[]) {
return arr.sort((a, b) => a.name.localeCompare(b.name))
return arr.sort((a, b) => a.name?.localeCompare(b.name))
}

// sort objects in array alphabeticly
Expand Down
55 changes: 42 additions & 13 deletions src/frontend/components/helpers/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,44 +200,73 @@ export async function getBase64Path(path: string, size: number = mediaSize.big)
// CACHE

const jpegQuality = 90 // 0-100
let capturing: string[] = []
let retries: any = {}
export function captureCanvas(data: any) {
if (capturing.includes(data.input)) return exit()
capturing.push(data.input)

let canvas = document.createElement("canvas")

let isImage: boolean = get(imageExtensions).includes(data.extension)
let mediaElem: any = document.createElement(isImage ? "img" : "video")
mediaElem.src = data.input

mediaElem.addEventListener(isImage ? "load" : "loadeddata", async () => {
if (!isImage) mediaElem.currentTime = mediaElem.duration * (data.seek ?? 0.5)

let mediaSize = isImage ? { width: mediaElem.naturalWidth, height: mediaElem.naturalHeight } : { width: mediaElem.videoWidth, height: mediaElem.videoHeight }
let newSize = getNewSize(mediaSize, data.size || {})
canvas.width = newSize.width
canvas.height = newSize.height

// seek video
if (!isImage) {
mediaElem.currentTime = mediaElem.duration * (data.seek ?? 0.5)
await wait(50)
}

// wait until loaded
let hasLoaded = await waitUntilValueIsDefined(() => (isImage ? mediaElem.complete : mediaElem.readyState === 4), 20)
if (!hasLoaded) {
send(MAIN, ["SAVE_IMAGE"], { path: data.output })
return
}
if (!hasLoaded) return exit()

await wait(50)
captureCanvas(mediaElem, mediaSize)
})

// this should not get called becaues the file is checked existing, but here in case
mediaElem.addEventListener("error", (err) => {
if (!mediaElem.src) return

console.error("Could not load media:", err)
if (!retries[data.input]) retries[data.input] = 0
retries[data.input]++

if (retries[data.input] > 2) return exit()
else setTimeout(() => (isImage ? "" : mediaElem.load()), 3000)
})

mediaElem.src = data.input
// document.body.appendChild(mediaElem) // DEBUG

async function captureCanvas(media, mediaSize) {
let ctx = canvas.getContext("2d")
if (!ctx) {
send(MAIN, ["SAVE_IMAGE"], { path: data.output })
return
}
if (!ctx) return exit()

// ensure lessons are downloaded and loaded before capturing
let isLessons = data.input.includes("Lessons")
let loading = isLessons ? 3000 : 200
await wait(loading)
ctx.drawImage(media, 0, 0, mediaSize.width, mediaSize.height, 0, 0, canvas.width, canvas.height)
await wait(50)

await wait(loading * 0.2)
let dataURL = canvas.toDataURL("image/jpeg", jpegQuality)

send(MAIN, ["SAVE_IMAGE"], { path: data.output, base64: dataURL })

// unload
capturing.splice(capturing.indexOf(data.input), 1)
mediaElem.src = ""
}

function exit() {
send(MAIN, ["SAVE_IMAGE"], { path: data.output })
}
}

Expand Down
Loading

0 comments on commit 47fc52d

Please sign in to comment.