Skip to content

Commit 207d147

Browse files
authored
Version 2.0.2
# Bug Fixes - File closed successfully but folders are not deleted #26 by implementing custom protocol video:// with media stream buffering - text is now on the top of the videos - fixed problem on opening new project multiple listeners are registered - dialog is show to show the file is saving - presenter view is now in focus in slideshow mode
2 parents d157bcd + 37c870e commit 207d147

7 files changed

Lines changed: 175 additions & 48 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "choirslides",
33
"productName": "ChoirSlides",
4-
"version": "2.0.1",
4+
"version": "2.0.2",
55
"description": "A software designed to create lyric slideshows. This software allows users to combine lyrics with background videos, resulting in a visually engaging slideshow that synchronizes the lyrics with the video footage.",
66
"main": "./.webpack/main",
77
"scripts": {

src/index.js

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {createPresentationView, createShowCreatorView, createPresenterView} from
77
import WorkingFile from './workingFile'
88
import log from 'electron-log/main';
99

10+
let VidFilestream;
11+
1012

1113
if (app.isPackaged) {
1214
log.initialize({spyRendererConsole: true});
@@ -27,13 +29,105 @@ if (require('electron-squirrel-startup')) {
2729
app.quit();
2830
}
2931

32+
protocol.registerSchemesAsPrivileged([
33+
{
34+
scheme: "media",
35+
privileges: {
36+
// secure: true,
37+
bypassCSP: true,
38+
stream: true,
39+
},
40+
},
41+
])
3042

3143
app.disableHardwareAcceleration()
3244

45+
function parseRangeRequests(text, size) {
46+
const token = text.split("=");
47+
if (token.length !== 2 || token[0] !== "bytes") {
48+
return [];
49+
}
50+
51+
return token[1]
52+
.split(",")
53+
.map((v) => parseRange(v, size))
54+
.filter(([start, end]) => !isNaN(start) && !isNaN(end) && start <= end);
55+
}
56+
57+
const NAN_ARRAY = [NaN, NaN];
58+
59+
function parseRange(text, size) {
60+
const token = text.split("-");
61+
if (token.length !== 2) {
62+
return NAN_ARRAY;
63+
}
64+
65+
const startText = token[0].trim();
66+
const endText = token[1].trim();
67+
68+
if (startText === "") {
69+
if (endText === "") {
70+
return NAN_ARRAY;
71+
} else {
72+
let start = size - Number(endText);
73+
if (start < 0) {
74+
start = 0;
75+
}
76+
77+
return [start, size - 1];
78+
}
79+
} else {
80+
if (endText === "") {
81+
return [Number(startText), size - 1];
82+
} else {
83+
let end = Number(endText);
84+
if (end >= size) {
85+
end = size - 1;
86+
}
87+
88+
return [Number(startText), end];
89+
}
90+
}
91+
}
92+
3393
// This method will be called when Electron has finished
3494
// initialization and is ready to create browser windows.
3595
// Some APIs can only be used after this event occurs.
3696
app.on('ready', () => {
97+
protocol.handle("media", (request) => {
98+
// https://github.com/electron/electron/issues/38749#issuecomment-1681531939
99+
const fp = path.join(currentProject.basePath, decodeURIComponent(request.url.slice('media://'.length)))
100+
const stats = fs.statSync(fp);
101+
102+
// console.log(fp, stats)
103+
const headers = new Headers();
104+
headers.set("Accept-Ranges", "bytes");
105+
headers.set("Content-Type", "video/mp4");
106+
107+
let status = 200;
108+
const rangeText = request.headers.get("range");
109+
110+
if (rangeText) {
111+
const ranges = parseRangeRequests(rangeText, stats.size);
112+
113+
const [start, end] = ranges[0];
114+
// console.log(rangeText, stats.size, start, end);
115+
headers.set("Content-Length", `${end - start + 1}`);
116+
headers.set("Content-Range", `bytes ${start}-${end}/${stats.size}`);
117+
status = 206;
118+
VidFilestream = fs.createReadStream(fp, {start, end});
119+
} else {
120+
headers.set("Content-Length", `${stats.size}`);
121+
VidFilestream = fs.createReadStream(fp);
122+
123+
}
124+
125+
return new Response(VidFilestream, {
126+
headers,
127+
status,
128+
});
129+
})
130+
37131
showCreatorView = createShowCreatorView()
38132
showCreatorView.on('close', (e) => {
39133
if (currentProject.isOpened) {
@@ -47,7 +141,10 @@ app.on('ready', () => {
47141
if (choice === 1) {
48142
e.preventDefault()
49143
} else {
50-
144+
if (currentProject.isOpened) {
145+
VidFilestream?.destroy()
146+
currentProject.closeProject()
147+
}
51148
}
52149
}
53150
})
@@ -57,8 +154,7 @@ app.on('ready', () => {
57154
// for applications and their menu bar to stay active until the user quits
58155
// explicitly with Cmd + Q.
59156
app.on('window-all-closed', () => {
60-
/*if (currentProject.isOpened)
61-
currentProject.closeProject()*/
157+
62158
if (process.platform !== 'darwin') {
63159
app.quit();
64160
}
@@ -93,9 +189,11 @@ ipcMain.handle("file-opened", async (e, data) => {
93189
let mainWindow = BrowserWindow.getFocusedWindow().getParentWindow();
94190
BrowserWindow.getFocusedWindow().destroy()
95191

96-
/*if (currentProject.isOpened) {
192+
if (currentProject.isOpened) {
193+
VidFilestream?.destroy()
194+
mainWindow.webContents.send("slideshow:destroy")
97195
currentProject.closeProject()
98-
}*/
196+
}
99197

100198
currentProject = new WorkingFile(data)
101199

@@ -104,14 +202,19 @@ ipcMain.handle("file-opened", async (e, data) => {
104202
} else {
105203
currentProject.openProject()
106204
}
107-
console.log(currentProject.toObject())
108-
mainWindow.setTitle(`ChoirSlide • ${currentProject.projectName}`)
205+
mainWindow.setTitle(`ChoirSlide - ${currentProject.projectName}`)
109206
mainWindow.webContents.send("file-params", currentProject.toObject());
110207
})
111208

112209
ipcMain.handle("file-save", (e, content) => {
210+
dialog.showMessageBox(BrowserWindow.fromId(e.frameId), {
211+
title: "File Save",
212+
message: "File Is Saving",
213+
type: "info",
214+
})
215+
113216
currentProject.saveProject(content).then(() => {
114-
dialog.showMessageBoxSync({
217+
dialog.showMessageBox(BrowserWindow.fromId(e.frameId), {
115218
title: "File Save",
116219
message: "File Saved Successfully",
117220
type: "info"
@@ -207,6 +310,7 @@ ipcMain.handle("slideshow:start", (e, content) => {
207310
presentationView.webContents.send("main:presentation", {type: "init", data})
208311
})
209312
showCreatorView.destroy()
313+
presenterView.focus()
210314
} else {
211315
dialog.showMessageBoxSync(showCreatorView, {
212316
type: 'error',

src/renderer/ShowCreateView/js/fileOpen.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ window.file.onFileParams(function (fileParams) {
1010
let slides = presentation.map(e => new Slide(e))
1111
let slidePreviewCanv = document.getElementById("currentSlideThumbCanvas");
1212
console.log(slidePreviewCanv)
13-
comm.toPresentation({type: "init", data: JSON.stringify(fileParams)})
13+
// comm.toPresentation({type: "init", data: JSON.stringify(fileParams)})
1414
present = new ShowCreator({
1515
container: "currentSlideThumbCanvas",
1616
sidebarSlidesContainer: document.getElementById("sidebarSlidesContainer"),
@@ -23,15 +23,6 @@ window.file.onFileParams(function (fileParams) {
2323
mode: fileParams.mode,
2424
sepBy: fileParams.sepBy
2525
})
26-
hotkeys('delete,ctrl+s', function (event, handler) {
27-
switch (handler.key) {
28-
case 'delete':
29-
present.removeSlide()
30-
break;
31-
case 'ctrl+s':
32-
file.save(present.saveShow())
33-
}
34-
})
3526

3627
// window.onbeforeunload = (e) => {
3728
// e.preventDefault()
@@ -41,15 +32,24 @@ window.file.onFileParams(function (fileParams) {
4132

4233

4334
})
35+
hotkeys('delete,ctrl+s', function (event, handler) {
36+
switch (handler.key) {
37+
case 'delete':
38+
present?.removeSlide()
39+
break;
40+
case 'ctrl+s':
41+
file.save(present.saveShow())
42+
}
43+
})
4444

45-
comm.onSlideshowInitialized(() => {
45+
window.comm.onSlideshowInitialized(() => {
4646
comm.startSlideshow(present.saveShow())
4747
})
4848

49-
comm.onSlideshowDestroy(() => {
50-
present.destroyShow()
51-
})
52-
window.onbeforeunload = () => {
53-
console.log("Destroying show")
49+
window.comm.onSlideshowDestroy(() => {
5450
present?.destroyCreator()
55-
}
51+
})
52+
// window.onbeforeunload = () => {
53+
// console.log("Destroying show")
54+
// present?.destroyCreator()
55+
// }

src/renderer/js/Classes/ShowClass.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class Show extends ShowPresentationBase {
2828
*/
2929
_createBackground(slide) {
3030
let VideoObj = document.createElement('video');
31-
// VideoObj.src = `media-loader://${encodeURIComponent(`${this.#basePath}/${slide.videoFile}`)}`
32-
VideoObj.src = `${this._basePath}/${slide.videoFileName}.${slide.videoFileFormat}`
31+
VideoObj.src = "media://" + encodeURIComponent(slide.videoFileName + "." + slide.videoFileFormat)
32+
// VideoObj.src = `${this._basePath}/${slide.videoFileName}.${slide.videoFileFormat}`
3333
VideoObj.muted = true
3434
VideoObj.loop = true
3535
VideoObj.preload = "auto"

src/renderer/js/Classes/ShowCreatorClass.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ class ShowCreator extends Konva.Stage {
3232
#basePath;
3333
#sideBarSlidesContainer;
3434
#addSlideBtn;
35-
#addVidBtn;
3635
#baseLayer;
3736
#background;
3837
#slideTextEditor;
3938
#slideTextInput;
4039
#videoObj;
4140
#anim;
4241
#filePicker;
42+
#controller;
4343

4444
#slidesRadioSelector() {
4545
return document.querySelectorAll(`#${this.#sideBarSlidesContainer.id} input`)
@@ -100,6 +100,7 @@ class ShowCreator extends Konva.Stage {
100100
}
101101

102102
#changeSlideText(e) {
103+
103104
// change slide text values and sidebar
104105
this.#slides[this.#currentSlide].text = e.target.value;
105106
this.#sideBarSlidesContainer.children[this.#currentSlide].querySelector("span").innerText = e.target.value
@@ -109,6 +110,7 @@ class ShowCreator extends Konva.Stage {
109110
/**
110111
* https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation
111112
*/
113+
112114
this.#currentSlide = this.#findSelectedSlidePos()
113115
this.#slideTextInput.value = this.#slides[this.#currentSlide].text
114116
this.#videoObj.src = "file://" + this.#basePath + "/" + this.#slides[this.#currentSlide].videoFileName + "." + this.#slides[this.#currentSlide].videoFileFormat
@@ -121,7 +123,7 @@ class ShowCreator extends Konva.Stage {
121123
let filePicker = document.createElement("input");
122124
filePicker.type = "file";
123125
filePicker.accept = "video/*";
124-
filePicker.addEventListener("change", this.#onVideoFilePicked.bind(this));
126+
filePicker.addEventListener("change", this.#onVideoFilePicked.bind(this), {signal: this.#controller.signal});
125127
return filePicker
126128
}
127129

@@ -137,6 +139,7 @@ class ShowCreator extends Konva.Stage {
137139
}
138140

139141
async #onVideoFilePicked(e) {
142+
140143
let filePicker = e.target
141144
console.log(filePicker.files.item(0))
142145
let file = filePicker.files.item(0)
@@ -157,7 +160,8 @@ class ShowCreator extends Konva.Stage {
157160
#setCanvasToVideo(/** Slide*/slide = this.#slides[this.#currentSlide]) {
158161
if (slide.videoFileName !== undefined) {
159162
this.container().style.background = "transparent"
160-
this.#videoObj.src = "file://" + this.#basePath + "/" + slide.videoFileName + "." + slide.videoFileFormat
163+
// this.#videoObj.src = "file://" + this.#basePath + "/" + slide.videoFileName + "." + slide.videoFileFormat
164+
this.#videoObj.src = "media://" + encodeURIComponent(slide.videoFileName + "." + slide.videoFileFormat)
161165
console.log(this.#videoObj)
162166
this.#background.setAttrs({
163167
image: this.#videoObj,
@@ -196,14 +200,15 @@ class ShowCreator extends Konva.Stage {
196200
this.#sideBarSlidesContainer = props.sidebarSlidesContainer
197201
this.#slideTextEditor = props.slideTextEditor
198202
this.#slideTextInput = this.#slideTextEditor.querySelector(`textarea`);
203+
this.#addSlideBtn = document.querySelector(`#slideAdd`);
204+
205+
this.#controller = new AbortController();
199206

200-
while (this.#sideBarSlidesContainer.firstChild && this.#sideBarSlidesContainer.removeChild(this.#sideBarSlidesContainer.firstChild)) ;
201207

202208
this.#basePath = props.basePath
203209
this.#slides = props.slides
204210

205211
console.log(this.#slides)
206-
this.#addSlideBtn = document.querySelector(`#slideAdd`);
207212

208213
this.#videoObj = document.createElement("video");
209214
this.#videoObj.autoplay = true;
@@ -217,11 +222,12 @@ class ShowCreator extends Konva.Stage {
217222
this.#baseLayer = new Konva.Layer({});
218223
this.add(this.#baseLayer)
219224

225+
220226
this.#addSlideBtn.addEventListener("click", () => {
221227
this.addNewSlide()
222-
})
223-
this.#sideBarSlidesContainer.addEventListener("change", this.#onSlideClick.bind(this))
224-
this.#slideTextInput.addEventListener("input", this.#changeSlideText.bind(this))
228+
}, {signal: this.#controller.signal})
229+
this.#sideBarSlidesContainer.addEventListener("change", this.#onSlideClick.bind(this), {signal: this.#controller.signal})
230+
this.#slideTextInput.addEventListener("input", this.#changeSlideText.bind(this), {signal: this.#controller.signal})
225231

226232
this.#anim = new Konva.Animation(function () {
227233
// do nothing, animation just need to update the layer
@@ -256,11 +262,9 @@ class ShowCreator extends Konva.Stage {
256262
// mapping slides must be at end
257263
this.#slides.forEach(slide => this.addNewSlide(slide))
258264

259-
260265
if (this.#slides.length === 0) {
261266
this.addNewSlide()
262267
}
263-
264268
}
265269

266270

@@ -316,10 +320,13 @@ class ShowCreator extends Konva.Stage {
316320
return JSON.stringify(this.#slides)
317321
}
318322

319-
destroyShow() {
323+
destroyCreator() {
320324
this.#videoObj.pause()
321325
this.#videoObj.src = ''
322326
this.#videoObj.load()
327+
while (this.#sideBarSlidesContainer.firstChild && this.#sideBarSlidesContainer.removeChild(this.#sideBarSlidesContainer.firstChild)) ;
328+
this.#controller.abort()
329+
this.destroy()
323330
}
324331
}
325332

src/renderer/js/Classes/ShowPresentationBaseClass.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class ShowPresentationBase extends Konva.Stage {
44
#w;
55
#h;
66
#textToHeightRatio = 0.125
7-
#textToSpacingRatio = .75
7+
#textToSpacingRatio = .15
88
_basePath;
99
#simpleText;
1010
#background;
@@ -110,7 +110,8 @@ class ShowPresentationBase extends Konva.Stage {
110110
});
111111

112112
this.#textBG = new Konva.Rect({
113-
x: this.#w / 2 - this.#simpleText.getTextWidth() / 2 - this.#padding / 2,
113+
// x: this.#w / 2 - this.#simpleText.getTextWidth() / 2 - this.#padding / 2,
114+
x: this.#simpleText.getClientRect().x - this.#padding / 2,
114115
y: this.#simpleText.getClientRect().y - this.#padding / 2,
115116
height: this.#simpleText.getClientRect().height + this.#padding,
116117
width: this.#simpleText.getTextWidth() + this.#padding,

0 commit comments

Comments
 (0)