Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"install": "node scripts/install.js",
"build": "node-gyp rebuild",
"demo": "node demo/demo.js",
"test": "node test/app-lifecycle.js && node test/animation.js && node test/layer-transform.js && node test/contents-gravity.js && node test/contents-center.js && node test/accessibility-display.js && node test/ignores-mouse-events.js && node test/popup-menu.js && node test/pasteboard-write.js && node test/surface-blit.js && node test/surface-faded.js && node test/image-smoothing.js && node test/surface-colorspace.js && node test/video-surface.js && node test/player.js && node test/round-rect-xy.js && node test/clip-rule.js && node test/radial-gradient.js && node test/control-bezels.js && node test/text-features.js && node test/font-from-data.js && node test/text-coverage.js && node test/text-typesetter.js && node test/text-line-height.js && node test/text-line-width.js && node test/text-justify.js && node test/text-scale.js && node test/symbols.js && node test/eventkit-permissions.js && node test/calendars.js && node test/calendar-writes.js && node test/screencolor.js && node test/notifications.js && node test/threaded.js && node test/threaded-verbs.js && node test/threaded-frames.js && node test/threaded-resize.js && node test/ending-worker.js && node test/live-resize.js && node test/window-states.js && node test/threaded-window-states.js && node test/activation-policy.js && node test/last-input.js"
"test": "node test/app-lifecycle.js && node test/animation.js && node test/layer-transform.js && node test/contents-gravity.js && node test/contents-center.js && node test/accessibility-display.js && node test/ignores-mouse-events.js && node test/popup-menu.js && node test/pasteboard-write.js && node test/surface-blit.js && node test/surface-faded.js && node test/image-smoothing.js && node test/surface-colorspace.js && node test/video-surface.js && node test/player.js && node test/player-vp9.js && node test/round-rect-xy.js && node test/clip-rule.js && node test/radial-gradient.js && node test/control-bezels.js && node test/text-features.js && node test/font-from-data.js && node test/text-coverage.js && node test/text-typesetter.js && node test/text-line-height.js && node test/text-line-width.js && node test/text-justify.js && node test/text-scale.js && node test/symbols.js && node test/eventkit-permissions.js && node test/calendars.js && node test/calendar-writes.js && node test/screencolor.js && node test/notifications.js && node test/threaded.js && node test/threaded-verbs.js && node test/threaded-frames.js && node test/threaded-resize.js && node test/ending-worker.js && node test/live-resize.js && node test/window-states.js && node test/threaded-window-states.js && node test/activation-policy.js && node test/last-input.js"
},
"dependencies": {
"node-addon-api": "^8.3.0"
Expand Down
14 changes: 14 additions & 0 deletions src/player.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include <napi.h>
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreVideo/CoreVideo.h>
#import <QuartzCore/QuartzCore.h>
Expand Down Expand Up @@ -259,8 +260,21 @@ static void Apply(CALPlayer* p, const PlayerSettings& s) {
}
}

// VP9 is one of VideoToolbox's supplemental decoders, which a process has to
// ask for before AVFoundation can use one. Without this a `vp09` track in an
// MP4 opens and comes ready, and reports a picture of no size and hands out
// no frames, with no error anywhere. Asked once, and of nothing on a Mac that
// has no such decoder: its VP9 then fails as any format it cannot play does.
static void RegisterSupplementalDecoders() {
static dispatch_once_t once;
dispatch_once(&once, ^{
VTRegisterSupplementalVideoDecoderIfAvailable(kCMVideoCodecType_VP9);
});
}

static Napi::Value CreatePlayer(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
RegisterSupplementalDecoders();
if (!info[0].IsString() || info[0].As<Napi::String>().Utf8Value().empty()) {
Napi::TypeError::New(env, "createPlayer(url, options): url is a path or a URL string")
.ThrowAsJavaScriptException();
Expand Down
Binary file added test/fixtures/clip-vp9.mp4
Binary file not shown.
77 changes: 77 additions & 0 deletions test/player-vp9.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';
// A `vp09` track in an MP4 (src/player.mm, RegisterSupplementalDecoders):
// VP9 is a decoder VideoToolbox hands out only to a process that asks, and
// without one the item comes ready with a picture of no size and no frames
// ever, with no error. This is that picture having its size, and a seek
// landing on a frame.
//
// test/fixtures/clip-vp9.mp4 is half a second of one colour, 160x90 VP9,
// written by
//
// ffmpeg -f lavfi -i color=c=0xE07030:s=160x90:r=30:d=0.5 \
// -c:v libvpx-vp9 -g 4 -tag:v vp09 -an -movflags +faststart clip-vp9.mp4
//
// A Mac with no such decoder has nothing to show, and CI's virtual ones may
// be that: there, and only there, no picture is news and not a failure.
//
// Exits 0 when every expectation held.

const path = require('path');
const { native } = require('..');

const CLIP = path.join(__dirname, 'fixtures', 'clip-vp9.mp4');

const fail = (msg, ...rest) => {
console.error('player-vp9:', msg, ...rest);
process.exit(1);
};

function pumpUntil(pred, ms = 5000) {
return new Promise((resolve, reject) => {
const deadline = Date.now() + ms;
const tick = setInterval(() => {
native.pump2();
if (pred()) {
clearInterval(tick);
resolve();
} else if (Date.now() > deadline) {
clearInterval(tick);
reject(new Error('timed out'));
}
}, 8);
});
}

async function main() {
native.initApp();
const events = [];
native.setBackendEventCallback((ev) => {
if (String(ev.type).startsWith('player-')) events.push(ev);
});
const { id } = native.createPlayer(CLIP, { muted: true });
const said = (type) => events.find((e) => e.id === id && e.type === type);
await pumpUntil(() => said('player-metadata') || said('player-error')).catch(
() => fail('the item never came ready'),
);
if (said('player-error')) fail('the clip failed', said('player-error'));
// the item can come ready first and find its size after
await pumpUntil(() => events.some((e) => e.type === 'player-metadata' && e.width), 1500).catch(() => {});
const sized = events.find((e) => e.type === 'player-metadata' && e.width);
if (!sized) {
if (process.env.CI) {
console.log('player-vp9: no VP9 decoder on this machine');
process.exit(0);
}
fail('a picture of no size: nothing asked for the VP9 decoder');
}
if (sized.width !== 160 || sized.height !== 90) fail('its size', sized);
native.playerSeek(id, 0.2);
await pumpUntil(() => events.some((e) => e.type === 'player-time' && e.currentTime > 0.15)).catch(
() => fail('the seek landed nowhere'),
);
native.releasePlayer(id);
console.log('player-vp9: ok');
process.exit(0);
}

main().catch((err) => fail(err));
Loading