Skip to content

Commit

Permalink
✔ Fixed Lessons.church
Browse files Browse the repository at this point in the history
- Tweaked media cache
- Fade out video audio
  • Loading branch information
vassbo committed Jun 26, 2024
1 parent 9150c26 commit 9a3fc1a
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 63 deletions.
109 changes: 83 additions & 26 deletions src/electron/data/downloadMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "path"
import { toApp } from ".."
import { MAIN } from "../../types/Channels"
import { dataFolderNames, doesPathExist, getDataFolder } from "../utils/files"
import { waitUntilValueIsDefined } from "../utils/helpers"

export function downloadMedia(lessons: any[]) {
let replace = lessons.map(checkLesson)
Expand All @@ -12,6 +13,10 @@ export function downloadMedia(lessons: any[]) {
}

function checkLesson(lesson: any) {
downloadCount = 0
failedDownloads = 0
toApp(MAIN, { channel: "LESSONS_DONE", data: { showId: lesson.showId, status: { finished: 0, failed: 0 } } })

const lessonsFolder = getDataFolder(lesson.path, dataFolderNames.lessons)
const lessonFolder = path.join(lessonsFolder, lesson.name)
fs.mkdirSync(lessonFolder, { recursive: true })
Expand All @@ -21,7 +26,7 @@ function checkLesson(lesson: any) {
let filePath = getFilePath(file)
if (!filePath) return

return downloadFile(filePath, file)
return downloadFile(filePath, file, lesson.showId)
})
.filter((a: any) => a)

Expand All @@ -45,99 +50,151 @@ function getFileExtension(url: string, fileType: string = "") {
return ""
}

function downloadFile(filePath: string, file: any) {
function downloadFile(filePath: string, file: any, showId: string) {
let fileRef = { from: file.url, to: filePath, type: file.type }

if (doesPathExist(filePath)) {
// console.log(filePath + " exists!")
downloadCount++
toApp(MAIN, { channel: "LESSONS_DONE", data: { showId, status: { finished: downloadCount, failed: failedDownloads } } })
return fileRef
}

addToDownloadQueue({ path: filePath, file })
addToDownloadQueue({ path: filePath, file, showId })

return fileRef
}

let downloadQueue: any[] = []
function addToDownloadQueue(file: any) {
let alreadyInQueue = downloadQueue.find((a) => a.path === file.path)
if (alreadyInQueue) {
downloadCount++
return
}

downloadQueue.push(file)
startDownload()
initDownload()
}

let downloading: any = null
let downloadCount: number = 0
let errorCount: number = 0
async function startDownload() {
if (downloading) return
let currentlyDownloading: number = 0
const maxAmount = 5
const refillMargin = maxAmount * 0.6
let waiting: boolean = false
async function initDownload() {
if (waiting) return

if (!downloadQueue.length) {
if (downloadCount) console.log(`${downloadCount} file(s) downloaded!`)
if (currentlyDownloading < 1 && downloadCount) {
console.log(`${downloadCount} file(s) downloaded!`)
downloadCount = 0
failedDownloads = 0

return
}

// return setTimeout(initDownload, 500)
return
}

let timeout = true
setTimeout(() => {
if (!timeout) return
next()
}, 8000)
// generate a max amount at the same time
if (currentlyDownloading > maxAmount) {
waiting = true
await waitUntilValueIsDefined(() => currentlyDownloading < refillMargin)
waiting = false
}

if (!downloadQueue[0]) {
downloadQueue.shift()
initDownload()
return
}

downloading = downloadQueue.shift()
currentlyDownloading++
startDownload(downloadQueue.shift())
}

let downloadCount: number = 0
let failedDownloads: number = 0
let errorCount: number = 0
async function startDownload(downloading: any) {
// download the media
const fileStream = fs.createWriteStream(downloading.path)
const file = downloading.file
let url = file.url

if (!url) return next()

const fileStream = fs.createWriteStream(downloading.path)
console.log(`Downloading lessons media: ${file.name}`)
https
.get(url, (res) => {
if (res.statusCode !== 200) {
console.error(`Failed to download file, status code: ${res.statusCode}`)
fileStream.close()
fs.unlink(downloading.path, () => {})

console.error(`Failed to download file, status code: ${res.statusCode}`)
failedDownloads++
toApp(MAIN, { channel: "LESSONS_DONE", data: { showId: downloading.showId, status: { finished: downloadCount, failed: failedDownloads } } })

next()
return
}

res.pipe(fileStream)

res.on("error", (err) => {
fileStream.close()
console.log(`Response error: ${err.message}`)

retry()
})

fileStream.on("error", (err) => {
fs.unlink(downloading.path, () => {})
console.error(`File error: ${err.message}`)

retry()
})

fileStream.on("finish", () => {
fileStream.close()
downloadCount++
console.error(`Finished downloading file: ${file.name}`)
toApp(MAIN, { channel: "LESSONS_DONE", data: { showId: downloading.showId, status: { finished: downloadCount, failed: failedDownloads } } })

next()
})
})
.on("error", (err) => {
fs.unlink(downloading.path, () => {})
fileStream.close()
console.error(`Request error: ${err.message}`)

retry()
})

function next() {
if (!timeout) return

timeout = false
downloading = null
startDownload()
clearTimeout(timeout)
currentlyDownloading--
initDownload()
}

function retry() {
if (errorCount > 5) return
if (errorCount > 5) {
failedDownloads++
toApp(MAIN, { channel: "LESSONS_DONE", data: { showId: downloading.showId, status: { finished: downloadCount, failed: failedDownloads } } })

next()
return
}
errorCount++

next()
addToDownloadQueue(downloading)
next()
}

let timeout = setTimeout(() => {
fileStream.close()
console.error(`File timed out: ${file.name}`)
next()
}, 60 * 8 * 1000) // 8 minutes timeout
}
4 changes: 2 additions & 2 deletions src/electron/data/thumbnails.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NativeImage, ResizeOptions, app, nativeImage } from "electron"
import fs from "fs"
import path from "path"
import { toApp } from ".."
import { isProd, toApp } from ".."
import { MAIN } from "../../types/Channels"
import { doesPathExist } from "../utils/files"
import { waitUntilValueIsDefined } from "../utils/helpers"
Expand Down Expand Up @@ -46,7 +46,7 @@ function generationFinished() {

let exists: string[] = []
async function generateThumbnail(data: Thumbnail) {
if (exists.includes(data.output)) return generationFinished()
if (isProd && exists.includes(data.output)) return generationFinished()
if (doesPathExist(data.output)) {
exists.push(data.output)
generationFinished()
Expand Down
12 changes: 12 additions & 0 deletions src/frontend/components/drawer/media/MediaLoader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@
$: if (path[0] === "/") path = `file://${path}`
$: useOriginal = hover || loadFullImage || retryCount > 5 || !thumbnailPath
// get duration
$: if (type === "video" && thumbnailPath) getVideoDuration()
function getVideoDuration() {
let video = document.createElement("video")
video.onloadeddata = () => {
duration = video.duration || 0
// video.pause()
video.src = ""
}
video.src = path
}
</script>

<div class="main" style="aspect-ratio: {customResolution.width}/{customResolution.height};" bind:offsetWidth={width} bind:offsetHeight={height}>
Expand Down
13 changes: 9 additions & 4 deletions src/frontend/components/helpers/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,32 +204,37 @@ export function captureCanvas(data: any) {
let canvas = document.createElement("canvas")

let isImage: boolean = get(imageExtensions).includes(data.extension)
console.log(isImage, data)
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 }
console.log(mediaSize)
let newSize = getNewSize(mediaSize, data.size || {})
canvas.width = newSize.width
canvas.height = newSize.height

// wait until loaded
await waitUntilValueIsDefined(() => (isImage ? mediaElem.complete : mediaElem.readyState === 4), 20)
let hasLoaded = await waitUntilValueIsDefined(() => (isImage ? mediaElem.complete : mediaElem.readyState === 4), 20)
if (!hasLoaded) return

console.log(data.input, hasLoaded)

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

function captureCanvas(media, mediaSize) {
async function captureCanvas(media, mediaSize) {
let ctx = canvas.getContext("2d")
if (!ctx) return

ctx.drawImage(media, 0, 0, mediaSize.width, mediaSize.height, 0, 0, canvas.width, canvas.height)
await wait(50)
let dataURL = canvas.toDataURL("image/jpeg", jpegQuality)

console.log(dataURL)

send(MAIN, ["SAVE_IMAGE"], { path: data.output, base64: dataURL })
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/components/helpers/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ function changeOutputBackground(data, { outs, output, id, i }) {
if (isVideo) videoStarting()
else if (previousWasVideo) videoEnding()

// wait for video receiver to change
setTimeout(() => {
// WIP data is sent directly in output, so this is probably not needed
// data is sent directly in output as well ??
send(OUTPUT, ["DATA"], { [id]: videoData })
if (data.startAt !== undefined) send(OUTPUT, ["TIME"], { [id]: data.startAt || 0 })
}, 100)
}, 600)

return data
}
Expand Down
1 change: 0 additions & 1 deletion src/frontend/components/helpers/show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export function getLabelId(label: string, replaceNumbers: boolean = true) {
label = label
.toLowerCase()
.replace(/x[0-9]/g, "") // x0-9
.replace(/[0-9]/g, "") // 0-9
.replace(/[[\]]/g, "") // []
.replace(/['":]/g, "") // '":
.trim()
Expand Down
7 changes: 4 additions & 3 deletions src/frontend/components/helpers/showActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,13 +667,14 @@ export function checkNextAfterMedia(endedId: string, type: "media" | "audio" | "
// check that current slide has the ended media!
if (type === "media" || type === "audio") {
let showMedia = _show(slideOut.id).media().get()
let currentMediaId = showMedia.find((a) => a.path === endedId)?.key
// find all matching paths because some slides with same background might have different media ids
let allMediaIds = showMedia.filter((a) => a.path === endedId).map((a) => a.key)

// don't go to next if current slide don't has outputted media
if (type === "media") {
if (layoutSlide.data?.background !== currentMediaId) return false
if (!allMediaIds.includes(layoutSlide.data?.background)) return false
} else if (type === "audio") {
if (!layoutSlide.data?.audio?.find((id) => id === currentMediaId)) return false
if (!layoutSlide.data?.audio?.find((id) => allMediaIds.includes(id))) return false
}
} else if (type === "timer") {
let slide = _show(slideOut.id).get("slides")[layoutSlide.id]
Expand Down
2 changes: 0 additions & 2 deletions src/frontend/components/main/popups/Alert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
send(MAIN, ["URL"], "https://freeshow.app/?download")
}
}
$: console.log(msg, msg.slice(msg.indexOf("#")))
</script>

<p on:click={click}>
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/output/layers/Background.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@
<div class="media" {style} class:key={isKeyOutput}>
{#if background1}
<div class="media" class:hidden={loading && !firstActive}>
<BackgroundMedia data={background1Data} fadingOut={firstFadingOut} {outputId} {transition} {currentStyle} {animationStyle} {duration} mirror={mirror || !firstActive} on:loaded={() => loaded(true)} />
<BackgroundMedia data={background1Data} fadingOut={firstFadingOut} {outputId} {transition} {currentStyle} {animationStyle} {duration} {mirror} on:loaded={() => loaded(true)} />
</div>
{/if}
{#if background2}
<div class="media" class:hidden={loading && firstActive}>
<BackgroundMedia data={background2Data} fadingOut={!firstFadingOut} {outputId} {transition} {currentStyle} {animationStyle} {duration} mirror={mirror || firstActive} on:loaded={() => loaded(false)} />
<BackgroundMedia data={background2Data} fadingOut={!firstFadingOut} {outputId} {transition} {currentStyle} {animationStyle} {duration} {mirror} on:loaded={() => loaded(false)} />
</div>
{/if}
</div>
Expand Down
Loading

0 comments on commit 9a3fc1a

Please sign in to comment.