Skip to content

Commit

Permalink
✨ Disabled slide icon
Browse files Browse the repository at this point in the history
- Fixed audio fade duration not working with decimals
- Bypass audio clear duration by clearing again
- Better show search
- Fixed freeze when pasting event item
- Fixed locate media files not working
- Media filters applied to style background
- Better edit history
- Fixed item background opacity input not loading
- Changed font preload
- Enter add show will place at project index
- Fix freeze when show in formatted incorrectly
- Fixed "Current output" not showing in stage output
- Fixed capture not sending frames
  • Loading branch information
vassbo committed Jul 17, 2024
1 parent 20c4dfb commit 0e20b80
Show file tree
Hide file tree
Showing 46 changed files with 255 additions and 148 deletions.
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<title>FreeShow</title>

<link rel="preload" href="./fonts/CMGSans-Regular.ttf" as="font" crossorigin="anonymous" />
<!-- <link rel="preload" href="./fonts/CMGSans-Regular.ttf" as="font" crossorigin="anonymous" /> -->
<link rel="stylesheet" href="./global.css" />
<link rel="stylesheet" href="./build/bundle.css" />

Expand Down
5 changes: 1 addition & 4 deletions src/electron/capture/CaptureHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class CaptureHelper {

return {
window,
subscribed: false,
frameSubscription: null,
displayFrequency: screen.displayFrequency || 60,
options: { ndi: false, server: false, stage: false },
framerates: defaultFramerates,
Expand All @@ -54,9 +54,6 @@ export class CaptureHelper {
CaptureTransmitter.startChannel(id, "ndi")
}
}

if (captureOptions.options.server) CaptureTransmitter.startChannel(id, "server")
if (captureOptions.options.stage) CaptureTransmitter.startChannel(id, "stage")
}

static getWindowScreen(window: BrowserWindow) {
Expand Down
2 changes: 1 addition & 1 deletion src/electron/capture/CaptureOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BrowserWindow } from "electron"
export type CaptureOptions = {
id: string
window: BrowserWindow
subscribed: boolean
frameSubscription: any
displayFrequency: number
options: any
framerates: any
Expand Down
27 changes: 16 additions & 11 deletions src/electron/capture/helpers/CaptureLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,38 @@ export class CaptureLifecycle {
if (!output.captureOptions) output.captureOptions = CaptureHelper.getDefaultCapture(window, id)

if (output.captureOptions) {
const capture = output.captureOptions
const captureOpts = output.captureOptions?.options || {}
Object.keys(toggle).map((key) => {
// turn off capture
if (capture.options[key] && !toggle[key]) CaptureTransmitter.stopChannel(id, key)
if (captureOpts[key] && !toggle[key]) CaptureTransmitter.stopChannel(id, key)
// set capture on/off
capture.options[key] = toggle[key]
captureOpts[key] = toggle[key]
})
output.captureOptions.options = captureOpts
}

CaptureHelper.updateFramerate(id)
if (!output.captureOptions || output.captureOptions.window.isDestroyed()) return

if (output.captureOptions.subscribed) return
CaptureHelper.updateFramerate(id)
CaptureHelper.Transmitter.startTransmitting(id)
output.captureOptions.subscribed = true

cpuCapture()
async function cpuCapture() {
if (output.captureOptions.frameSubscription) clearTimeout(output.captureOptions.frameSubscription)

captureFrame()
async function captureFrame() {
if (!output.captureOptions || output.captureOptions.window.isDestroyed()) return

let image = await output.captureOptions.window.webContents.capturePage()
processFrame(image)

if (!output.captureOptions) return

let frameRate = output.captureOptions.framerates.ndi
if (output.captureOptions.framerates.server > frameRate) frameRate = output.captureOptions.framerates.server
if (output.captureOptions.framerates.stage > frameRate) frameRate = output.captureOptions.framerates.stage

const ms = Math.round(1000 / frameRate)
setTimeout(cpuCapture, ms)
output.captureOptions.frameSubscription = setTimeout(captureFrame, ms)
}

function processFrame(image: NativeImage) {
Expand Down Expand Up @@ -80,9 +84,10 @@ export class CaptureLifecycle {
})

function endSubscription() {
if (!capture?.subscribed) return
if (!capture?.frameSubscription) return

capture.subscribed = false
clearTimeout(capture.frameSubscription)
capture.frameSubscription = null
}

function removeListeners() {
Expand Down
8 changes: 6 additions & 2 deletions src/electron/capture/helpers/CaptureTransmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export type Channel = {
timer: NodeJS.Timeout
lastImage: NativeImage
}

export class CaptureTransmitter {
static stageWindows: string[] = []
static requestList: any[] = []
Expand Down Expand Up @@ -43,7 +42,7 @@ export class CaptureTransmitter {

static startChannel(captureId: string, key: string) {
const combinedKey = `${captureId}-${key}`
const interval = 1000 / OutputHelper.getOutput(captureId)?.captureOptions?.framerates[key] || 10
const interval = 1000 / OutputHelper.getOutput(captureId)?.captureOptions?.framerates[key] || 30

if (this.channels[combinedKey]?.timer) {
clearInterval(this.channels[combinedKey].timer)
Expand All @@ -60,17 +59,22 @@ export class CaptureTransmitter {

static stopChannel(captureId: string, key: string) {
const combinedKey = `${captureId}-${key}`
if (!this.channels[combinedKey].timer) return

clearInterval(this.channels[combinedKey].timer)
}

static handleChannelInterval(captureId: string, key: string) {
const combinedKey = `${captureId}-${key}`
const channel = this.channels[combinedKey]
if (!channel) return

const image = CaptureHelper.storedFrames[captureId]
if (!image || channel.lastImage === image) return

const size = image.getSize()
channel.lastImage = image

switch (key) {
//case "preview":
//this.sendBufferToPreview(channel.captureId, image, { size })
Expand Down
1 change: 0 additions & 1 deletion src/electron/output/OutputHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export class OutputHelper {
SET_VALUE: (data: any) => OutputHelper.Values.updateValue(data),
TO_FRONT: (data: any) => OutputHelper.Bounds.moveToFront(data),

//PREVIEW_RESOLUTION: (data: any) => () => {}, //TODO: Eliminate? Was -> updatePreviewResolution(data)
REQUEST_PREVIEW: (data: any) => CaptureHelper.Transmitter.requestPreview(data),
CAPTURE: (data: any) => CaptureHelper.Lifecycle.startCapture(data.id, data.captures),

Expand Down
6 changes: 5 additions & 1 deletion src/electron/output/helpers/OutputLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export class OutputLifecycle {
static async createOutput(output: Output) {
let id: string = output.id || ""

if (OutputHelper.getOutput(id)) return this.removeOutput(id, output)
if (OutputHelper.getOutput(id)) {
CaptureHelper.Lifecycle.stopCapture(id)
this.removeOutput(id, output)
return
}

const outputWindow = this.createOutputWindow({ ...output.bounds, alwaysOnTop: output.alwaysOnTop !== false, kiosk: output.kioskMode === true, backgroundColor: output.transparent ? "#00000000" : "#000000" }, id, output.name)
//const previewWindow = this.createPreviewWindow({ ...output.bounds, backgroundColor: "#000000" })
Expand Down
1 change: 0 additions & 1 deletion src/electron/output/helpers/OutputVisibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export class OutputVisibility {
window.setKiosk(false)
window.hide()

// WIP has to restart because window is unresponsive when hidden again (until showed again)...
// this is only needed if the output is being captured!! (has to reset for capture to work when window is hidden)
let captureEnabled = Object.values(OutputHelper.getOutput(data.id)?.captureOptions?.options || {}).find((a) => a === true)
if (!captureEnabled) return
Expand Down
1 change: 1 addition & 0 deletions src/electron/utils/menuTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function template(strings: any): any {
? [
// { label: lang.actions?.pasteAndMatchStyle || "Paste And Match Style", role: "pasteAndMatchStyle", click: () => mc("paste") },
{ label: strings.actions?.delete || "Delete", click: () => mc("delete") },
// WIP: these shortcuts (CMD+A) not working in the MAC file selector modal
{ label: strings.actions?.selectAll || "Select All", click: () => mc("selectAll") }, // , accelerator: "CmdOrCtrl+A"
]
: [
Expand Down
9 changes: 9 additions & 0 deletions src/frontend/MainOutput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
{:else if loaded}
<Output {outputId} style={getStyleResolution(resolution, width, height, "fit")} />
{/if}

<!-- preload CMGSans font -->
{#if !loaded}<div class="fontPreload">.</div>{/if}
</div>

<style>
Expand Down Expand Up @@ -89,4 +92,10 @@
.fill.hideCursor {
cursor: none;
}
.fontPreload {
font-family: "CMGSans";
position: absolute;
opacity: 0;
}
</style>
5 changes: 3 additions & 2 deletions src/frontend/components/drawer/Drawer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@
if ($activeDrawerTab !== "shows") return
searchElem.select()
if ($activePage === "show") history({ id: "UPDATE", newData: { key: "shows", index: -1, data: { id: firstMatch.id } }, oldData: { id: $activeProject }, location: { page: "show", id: "project_ref" } })
activeShow.set({ ...firstMatch, index: $projects[$activeProject].shows.length - 1 })
let newIndex = ($activeShow?.index ?? $projects[$activeProject].shows.length - 1) + 1
if ($activePage === "show") history({ id: "UPDATE", newData: { key: "shows", index: newIndex, data: { id: firstMatch.id } }, oldData: { id: $activeProject }, location: { page: "show", id: "project_ref" } })
activeShow.set({ ...firstMatch, index: newIndex })
searchValue = ""
}
}
Expand Down
16 changes: 4 additions & 12 deletions src/frontend/components/drawer/media/MediaLoader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
video.src = path
}, 20)
}
$: mediaStyleString = `pointer-events: none;position: absolute;width: 100%;height: 100%;filter: ${mediaStyle.filter || ""};object-fit: ${mediaStyle.fit || "contain"};transform: scale(${mediaStyle.flipped ? "-1" : "1"}, ${mediaStyle.flippedY ? "-1" : "1"});`
</script>

<div class="main" style="aspect-ratio: {customResolution.width}/{customResolution.height};" bind:offsetWidth={width} bind:offsetHeight={height}>
Expand All @@ -98,21 +100,11 @@
{:else}
{#if type !== "video" || (thumbnailPath && retryCount <= 5)}
{#key retryCount}
<img
src={type !== "video" && useOriginal ? path : thumbnailPath}
alt={name}
style="pointer-events: none;position: absolute;filter: {mediaStyle.filter || ''};object-fit: {mediaStyle.fit || 'contain'};transform: scale({mediaStyle.flipped ? '-1' : '1'}, {mediaStyle.flippedY
? '-1'
: '1'});width: 100%;height: 100%;"
loading="lazy"
class:loading={!loaded}
on:error={reload}
on:load={() => (loaded = true)}
/>
<img src={type !== "video" && useOriginal ? path : thumbnailPath} alt={name} style={mediaStyleString} loading="lazy" class:loading={!loaded} on:error={reload} on:load={() => (loaded = true)} />
{/key}
{/if}
{#if type === "video" && useOriginal}
<video style="pointer-events: none;position: absolute;width: 100%;height: 100%;object-fit: {mediaStyle.fit};" bind:this={videoElem} on:error={reload} src={path} on:canplaythrough={getCurrentDuration}>
<video style={mediaStyleString} bind:this={videoElem} on:error={reload} src={path} on:canplaythrough={getCurrentDuration}>
<track kind="captions" />
</video>
{/if}
Expand Down
6 changes: 2 additions & 4 deletions src/frontend/components/drawer/timers/Timers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { activePopup, activeProject, activeTimers, dictionary, labelsDisabled, projects, showsCache, timers } from "../../../stores"
import Icon from "../../helpers/Icon.svelte"
import T from "../../helpers/T.svelte"
import { sortByName } from "../../helpers/array"
import { clone, keysToID, sortByName } from "../../helpers/array"
import Button from "../../inputs/Button.svelte"
import Slider from "../../inputs/Slider.svelte"
import Timer from "../../slide/views/Timer.svelte"
Expand All @@ -12,9 +12,7 @@
export let searchValue
$: globalList = Object.entries($timers).map(([id, a]) => ({ ...a, id }))
$: sortedTimers = sortByName(globalList)
$: sortedTimers = clone(sortByName(keysToID($timers)))
$: sortedTimersWithProject = sortedTimers.sort((a, b) => (list.includes(a.id) && !list.includes(b.id) ? -1 : 1))
$: filteredTimers = searchValue.length > 1 ? sortedTimersWithProject.filter((a) => a.name.toLowerCase().includes(searchValue.toLowerCase())) : sortedTimersWithProject
Expand Down
16 changes: 3 additions & 13 deletions src/frontend/components/edit/Editor.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
<script lang="ts">
import { onMount } from "svelte"
import { activeEdit, activeShow, drawer, editHistory, refreshEditSlide } from "../../stores"
import OverlayEditor from "./editors/OverlayEditor.svelte"
import SlideEditor from "./editors/SlideEditor.svelte"
import { activeEdit, drawer, refreshEditSlide } from "../../stores"
import Splash from "../main/Splash.svelte"
import EffectEditor from "./editors/EffectEditor.svelte"
import MediaEditor from "./editors/MediaEditor.svelte"
import OverlayEditor from "./editors/OverlayEditor.svelte"
import SlideEditor from "./editors/SlideEditor.svelte"
import TemplateEditor from "./editors/TemplateEditor.svelte"
$: showIsActive = $activeShow && ($activeShow.type || "show") === "show"
$: noEditSlide = $activeEdit.slide === null || $activeEdit.slide === undefined
$: if (showIsActive && noEditSlide) updateEditItem()
function updateEditItem() {
// set to show if: media has been opened AND show has not been opened
if ($activeEdit.id && (!$editHistory.find((a) => $activeEdit.id === a.edit?.id) || $editHistory.find((a) => $activeShow?.id === a.show?.id))) return
activeEdit.set({ slide: 0, items: [] })
}
$: if ($refreshEditSlide) {
setTimeout(() => {
refreshEditSlide.set(false)
Expand Down
8 changes: 6 additions & 2 deletions src/frontend/components/edit/Navigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,17 @@
return a
})
}
let clonedHistory: any[] = []
// don't change order when changing edits
$: if ($editHistory.length !== clonedHistory.length || !$activeEdit.id) setTimeout(() => (clonedHistory = clone($editHistory).reverse()))
</script>

{#if $activeEdit.id || (!$activeShow && $editHistory.length)}
<h3><T id="edit.recent" /></h3>
{#if $editHistory.length}
<div class="edited">
{#each clone($editHistory).reverse() as edited, i}
{#each clonedHistory as edited}
<div class="item">
<Button
style="width: 100%;"
Expand All @@ -81,7 +85,7 @@
refreshEditSlide.set(true)
if (edited.show) activeShow.set(edited.show)
}}
active={i === 0 && !!$activeEdit.id}
active={$activeEdit.id === edited.id}
bold={false}
border
>
Expand Down
3 changes: 1 addition & 2 deletions src/frontend/components/edit/editors/SlideEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
$: layoutSlide = ref?.[$activeEdit.slide!]?.data || {}
// get backgruond
$: bgId = layoutSlide.background
// $: loadFullImage = !!layoutSlide.background
let loadFullImage = true
let loadFullImage = false // true
// get ghost background
$: if (!bgId) {
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/components/edit/scripts/itemHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { history } from "../../helpers/history"
import { _show } from "../../helpers/shows"
import { getStyles, removeText } from "../../helpers/style"
import { addSlideAction } from "../../actions/actions"
import { keysToID, sortByName } from "../../helpers/array"

export function addItem(type: ItemType, id: any = null, options: any = {}) {
let activeTemplate: string | null = get(activeShow)?.id ? get(showsCache)[get(activeShow)!.id!]?.settings?.template : null
Expand All @@ -21,8 +22,8 @@ export function addItem(type: ItemType, id: any = null, options: any = {}) {
if (type === "list") newData.list = { items: [] }
// else if (type === "timer") newData.timer = { id: uid(), name: get(dictionary).timer?.counter || "Counter", type: "counter", start: 300, end: 0 }
else if (type === "timer") {
newData.timerId = Object.keys(get(timers))[0] || createNewTimer()
addSlideAction(get(activeEdit).slide ?? -1, "start_slide_timers")
newData.timerId = sortByName(keysToID(get(timers)))[0]?.id || createNewTimer()
if (get(timers)[newData.timerId || ""]?.type === "counter") addSlideAction(get(activeEdit).slide ?? -1, "start_slide_timers")
} else if (type === "clock") newData.clock = { type: "digital", seconds: false }
else if (type === "mirror") newData.mirror = {}
else if (type === "media") newData.src = options.src || ""
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/components/edit/tools/BoxStyle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
else if (id === "timer") box.edit.default[2].hidden = item?.timer?.viewType !== "circle"
else if (id === "variable") box.edit.default[0].value = item?.variable?.id
else if (id === "web") box.edit.default[0].value = item?.web?.src || ""
else if (id === "events") {
else if (id === "events" && box.edit.default[5]) {
box.edit.default[4].hidden = !item?.events?.enableStartDate
box.edit.default[5].hidden = !item?.events?.enableStartDate
}
Expand Down
19 changes: 10 additions & 9 deletions src/frontend/components/edit/tools/ItemStyle.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
<script lang="ts">
import { onMount } from "svelte"
import { uid } from "uid"
import type { Item } from "../../../../types/Show"
import { activeEdit, activeShow, selected, showsCache } from "../../../stores"
import { clone } from "../../helpers/array"
import { hexToRgb, splitRgb } from "../../helpers/color"
import { history } from "../../helpers/history"
import { _show } from "../../helpers/shows"
import { getFilters, getStyles } from "../../helpers/style"
import { addFilterString, addStyleString } from "../scripts/textStyle"
import { itemEdits } from "../values/item"
import EditValues from "./EditValues.svelte"
import { clone } from "../../helpers/array"
import { uid } from "uid"
export let allSlideItems: Item[]
export let item: Item | null
Expand All @@ -36,18 +35,20 @@
})
}
onMount(() => {
getBackgroundOpacity()
})
$: if (item) getBackgroundOpacity()
// background opacity
function getBackgroundOpacity() {
let backgroundValue = data["background-color"] || ""
if (!backgroundValue.includes("rgb")) return
let rgb = splitRgb(backgroundValue)
let boIndex = itemEditValues.style.findIndex((a) => a.id === "background-opacity")
if (boIndex < 0) return
if (!backgroundValue.includes("rgb")) {
itemEditValues.style[boIndex].value = 1
return
}
let rgb = splitRgb(backgroundValue)
itemEditValues.style[boIndex].value = rgb.a
}
function getOldOpacity() {
Expand Down
Loading

0 comments on commit 0e20b80

Please sign in to comment.