Skip to content

Commit 69906fc

Browse files
JustAMandmitrylyzo
authored andcommitted
Begin implementing render on demand
Cherry-picked from: jellyfin@848a686
1 parent 9424e8f commit 69906fc

File tree

5 files changed

+152
-26
lines changed

5 files changed

+152
-26
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ src/subtitles-octopus-worker.bc: $(OCTP_DEPS) src/Makefile src/SubtitleOctopus.c
299299
# Dist Files
300300
EMCC_COMMON_ARGS = \
301301
$(GLOBAL_CFLAGS) \
302-
-s EXPORTED_FUNCTIONS="['_main', '_malloc']" \
302+
-s EXPORTED_FUNCTIONS="['_main', '_malloc', '_libassjs_find_next_event_start', '_libassjs_find_event_stop_times']" \
303303
-s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap', 'getValue', 'FS_createPreloadedFile', 'FS_createPath']" \
304304
-s NO_EXIT_RUNTIME=1 \
305305
--use-preload-plugins \

src/SubtitleOctopus.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,69 @@ typedef struct {
8585
unsigned char* image;
8686
} RenderBlendResult;
8787

88+
double libassjs_find_next_event_start(double tm) {
89+
if (!track || track->n_events == 0) return -1;
90+
91+
ASS_Event *cur = track->events;
92+
long long now = (long long)(tm * 1000);
93+
long long closest = -1;
94+
95+
for (int i = 0; i < tracks->n_events; i++, cur++) {
96+
long long start = cur->Start;
97+
if (start >= now && (start < closest || closest == -1)) {
98+
closest = start;
99+
}
100+
}
101+
102+
return closest / 1000.0;
103+
}
104+
105+
void libassjs_find_event_stop_times(double tm, double *eventFinish, double *emptyFinish) {
106+
if (!track || track->n_events == 0) {
107+
*eventFinish = *emptyFinish = -1;
108+
return;
109+
}
110+
111+
ASS_Event *cur = track->events;
112+
long long now = (long long)(tm * 1000);
113+
114+
long long minFinish = -1, maxFinish = -1, minStart = -1;
115+
116+
for (int i = 0; i < track->n_events; i++, cur++) {
117+
long long start = cur->Start;
118+
long long finish = start + cur->Duration;
119+
if (start <= now) {
120+
if (finish > now) {
121+
if (finish < minFinish || minFinish == -1) {
122+
minFinish = finish;
123+
}
124+
if (finish > maxFinish) {
125+
maxFinish = finish;
126+
}
127+
}
128+
} else if (start < minStart || minStart == -1) {
129+
minStart = start;
130+
}
131+
}
132+
133+
if (minFinish != -1) {
134+
// some event is going on, so we need to re-draw either when it stops
135+
// or when some other event starts
136+
*eventFinish = ((minFinish < minStart) ? minFinish : minStart) / 1000.0;
137+
} else {
138+
// there's no current event, so no need to draw anything
139+
*eventFinish = -1;
140+
}
141+
142+
if (minFinish == maxFinish && (minStart == -1 || minStart > maxFinish)) {
143+
// there's empty space after this event ends
144+
*emptyFinish = minStart / 1000.0;
145+
} else {
146+
// there's no empty space after eventFinish happens
147+
*emptyFinish = *eventFinish;
148+
}
149+
}
150+
88151
class SubtitleOctopus {
89152
public:
90153
ASS_Library* ass_library;

src/post-worker.js

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ self.nextIsRaf = false;
88
self.lastCurrentTimeReceivedAt = Date.now();
99
self.targetFps = 30;
1010
self.libassMemoryLimit = 0; // in MiB
11+
self.renderOnDemand = false; // determines if only rendering on demand
1112

1213
self.width = 0;
1314
self.height = 0;
@@ -86,15 +87,19 @@ self.setTrack = function (content) {
8687
// Tell libass to render the new track
8788
self.octObj.createTrack("/sub.ass");
8889
self.ass_track = self.octObj.track;
89-
self.getRenderMethod()();
90+
if (!self.renderOnDemand) {
91+
self.getRenderMethod()();
92+
}
9093
};
9194

9295
/**
9396
* Remove subtitle track.
9497
*/
9598
self.freeTrack = function () {
9699
self.octObj.removeTrack();
97-
self.getRenderMethod()();
100+
if (!self.renderOnDemand) {
101+
self.getRenderMethod()();
102+
}
98103
};
99104

100105
/**
@@ -135,10 +140,14 @@ self.setCurrentTime = function (currentTime) {
135140
self.lastCurrentTimeReceivedAt = Date.now();
136141
if (!self.rafId) {
137142
if (self.nextIsRaf) {
138-
self.rafId = self.requestAnimationFrame(self.getRenderMethod());
143+
if (!self.renderOnDemand) {
144+
self.rafId = self.requestAnimationFrame(self.getRenderMethod());
145+
}
139146
}
140147
else {
141-
self.getRenderMethod()();
148+
if (!self.renderOnDemand) {
149+
self.getRenderMethod()();
150+
}
142151

143152
// Give onmessage chance to receive all queued messages
144153
setTimeout(function () {
@@ -163,7 +172,9 @@ self.setIsPaused = function (isPaused) {
163172
}
164173
else {
165174
self.lastCurrentTimeReceivedAt = Date.now();
166-
self.rafId = self.requestAnimationFrame(self.getRenderMethod());
175+
if (!self.renderOnDemand) {
176+
self.rafId = self.requestAnimationFrame(self.getRenderMethod());
177+
}
167178
}
168179
}
169180
};
@@ -191,39 +202,77 @@ self.render = function (force) {
191202
}
192203
};
193204

194-
self.blendRender = function (force) {
195-
self.rafId = 0;
196-
self.renderPending = false;
205+
self.blendRenderTiming(timing, force) {
197206
var startTime = performance.now();
198207

199208
var renderResult = self.octObj.renderBlend(self.getCurrentTime() + self.delay, force);
200-
if (renderResult.changed != 0 || force) {
201-
var canvases = [];
202-
var buffers = [];
203209

204-
if (renderResult.image) {
205-
// make a copy, as we should free the memory so subsequent calls can utilize it
206-
var result = new Uint8Array(HEAPU8.subarray(renderResult.image, renderResult.image + renderResult.dest_width * renderResult.dest_height * 4));
210+
var canvases = [];
211+
var buffers = [];
207212

208-
canvases = [{w: renderResult.dest_width, h: renderResult.dest_height, x: renderResult.dest_x, y: renderResult.dest_y, buffer: result.buffer}];
209-
buffers = [result.buffer];
210-
}
213+
if (renderResult.image) {
214+
// make a copy, as we should free the memory so subsequent calls can utilize it
215+
var result = new Uint8Array(HEAPU8.subarray(renderResult.image, renderResult.image + renderResult.dest_width * renderResult.dest_height * 4));
216+
217+
canvases = [{w: renderResult.dest_width, h: renderResult.dest_height, x: renderResult.dest_x, y: renderResult.dest_y, buffer: result.buffer}];
218+
buffers = [result.buffer];
219+
}
220+
221+
return {
222+
changed: renderResult.changed || force || false,
223+
time: Date.now(),
224+
spentTime: performance.now() - startTime,
225+
blendTime: renderResult.blend_time,
226+
canvases: canvases,
227+
buffers: buffers
228+
}
229+
}
211230

231+
self.blendRender = function (force) {
232+
self.rafId = 0;
233+
self.renderPending = false;
234+
235+
var rendered = self.blendRenderTiming(self.getCurrentTime() + self.delay, force);
236+
if (rendered.changed) {
212237
postMessage({
213238
target: 'canvas',
214239
op: 'renderCanvas',
215-
time: Date.now(),
216-
spentTime: performance.now() - startTime,
217-
blendTime: renderResult.blend_time,
218-
canvases: canvases
219-
}, buffers);
240+
time: rendered.time,
241+
spentTime: rendered.spentTime,
242+
blendTime: rendered.blendTime,
243+
canvases: rendered.canvases
244+
}, rendered.buffers);
220245
}
221246

222247
if (!self._isPaused) {
223248
self.rafId = self.requestAnimationFrame(self.blendRender);
224249
}
225250
};
226251

252+
self.oneshotRender = function (lastRenderedTime, renderNow) {
253+
var eventStart = renderNow ? lastRenderedTime : self._find_next_event_start(lastRenderedTime);
254+
var eventFinish = -1.0, emptyFinish = -1.0;
255+
var rendered = {};
256+
if (eventStart >= 0) {
257+
self._libassjs_find_event_stop_times(eventStart, self.eventFinish, self.emptyFinish);
258+
eventFinish = Module.getValue(self.eventFinish, 'double');
259+
emptyFinish = Module.getValue(self.emptyFinish, 'double');
260+
261+
rendered = self.blendRenderTiming(eventStart, true);
262+
}
263+
264+
postMessage({
265+
target: 'canvas',
266+
op: 'oneshot-result',
267+
eventStart: eventStart,
268+
eventFinish: eventFinish,
269+
emptyFinish: emptyFinish,
270+
spentTime: rendered.spentTime || 0,
271+
blendTime: rendered.blendTime || 0,
272+
canvases: rendered.canvases || []
273+
}, rendered.buffers || []);
274+
}
275+
227276
self.fastRender = function (force) {
228277
self.rafId = 0;
229278
self.renderPending = false;
@@ -508,7 +557,9 @@ function onMessageFromMainEmscriptenThread(message) {
508557
Module.canvas.boundingClientRect = message.data.boundingClientRect;
509558
}
510559
self.resize(message.data.width, message.data.height);
511-
self.getRenderMethod()();
560+
if (!self.renderOnDemand) {
561+
self.getRenderMethod()();
562+
}
512563
} else throw 'ey?';
513564
break;
514565
}
@@ -552,12 +603,16 @@ function onMessageFromMainEmscriptenThread(message) {
552603
self.targetFps = message.data.targetFps || self.targetFps;
553604
self.libassMemoryLimit = message.data.libassMemoryLimit || self.libassMemoryLimit;
554605
self.libassGlyphLimit = message.data.libassGlyphLimit || 0;
606+
self.renderOnDemand = message.data.renderOnDemand || false;
555607
removeRunDependency('worker-init');
556608
postMessage({
557609
target: "ready",
558610
});
559611
break;
560612
}
613+
case 'oneshot':
614+
self.oneshotRender(message.data.lastRendered, message.data.renderNow || false);
615+
break;
561616
case 'destroy':
562617
self.octObj.quitLibrary();
563618
break;

src/pre-worker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,19 @@ Module["preRun"].push(function () {
100100
Module['onRuntimeInitialized'] = function () {
101101
self.octObj = new Module.SubtitleOctopus();
102102

103+
self._find_next_event_start = Module['cwrap']('_libassjs_find_next_event_start', 'number', ['number']);
104+
self._find_event_stop_times = Module['cwrap']('_libassjs_find_event_stop_times', null, ['number', 'number', 'number']);
105+
103106
self.changed = Module._malloc(4);
104107
self.blendTime = Module._malloc(8);
105108
self.blendX = Module._malloc(4);
106109
self.blendY = Module._malloc(4);
107110
self.blendW = Module._malloc(4);
108111
self.blendH = Module._malloc(4);
109112

113+
self.eventFinish = Module._malloc(8);
114+
self.emptyFinish = Module._malloc(8);
115+
110116
self.octObj.initLibrary(screen.width, screen.height);
111117
self.octObj.createTrack("/sub.ass");
112118
self.ass_track = self.octObj.track;

src/subtitles-octopus.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ var SubtitlesOctopus = function (options) {
1313

1414
var self = this;
1515
self.canvas = options.canvas; // HTML canvas element (optional if video specified)
16-
self.renderMode = options.lossyRender ? 'fast' : (options.blendRender ? 'blend' : 'normal');
16+
self.renderMode = options.renderMode || (options.lossyRender ? 'fast' : (options.blendRender ? 'blend' : 'normal'));
1717
self.libassMemoryLimit = options.libassMemoryLimit || 0;
1818
self.libassGlyphLimit = options.libassGlyphLimit || 0;
1919
self.targetFps = options.targetFps || undefined;
20+
self.renderAhead = options.renderAhead || 0; // how many frames to render ahead and store; 0 to disable
2021
self.isOurCanvas = false; // (internal) we created canvas and manage it
2122
self.video = options.video; // HTML video element (optional if canvas specified)
2223
self.canvasParent = null; // (internal) HTML canvas parent element
@@ -110,7 +111,8 @@ var SubtitlesOctopus = function (options) {
110111
debug: self.debug,
111112
targetFps: self.targetFps,
112113
libassMemoryLimit: self.libassMemoryLimit,
113-
libassGlyphLimit: self.libassGlyphLimit
114+
libassGlyphLimit: self.libassGlyphLimit,
115+
renderOnDemand: renderAhead > 0
114116
});
115117
};
116118

0 commit comments

Comments
 (0)