diff --git a/pb_migrations/1795000000_benchmark_clips.js b/pb_migrations/1795000000_benchmark_clips.js new file mode 100644 index 0000000..86492ae --- /dev/null +++ b/pb_migrations/1795000000_benchmark_clips.js @@ -0,0 +1,69 @@ +/// + +// The decode ladder the device benchmark plays. +// +// One row per rung. The app downloads a rung (which measures the link, on a +// file big enough to escape TCP slow start) and then plays it from local +// storage (which measures the decoder, with the network out of the picture). +// Keeping those two apart is the whole point: on the owner's father's box the +// connection is fine and the hardware is not, and a test that streamed while it +// measured would blame the wrong one. +// +// Why a collection rather than files baked into amber-api: 76 MB does not +// belong in a Docker image that is rebuilt on every push, and a ladder that can +// be re-cut without shipping a release is a ladder that will actually be +// maintained. +// +// The clips are cut from Big Buck Bunny (c) Blender Foundation, +// peach.blender.org, CC-BY 3.0. Same ten seconds of high-motion footage at every +// rung, so only resolution, codec and bitrate differ. Each carries an E-AC3 5.1 +// track on purpose: the television decodes compressed audio in-app through +// FFmpeg, so that cost is part of what "can this box play this file" means. +// +// The file is deliberately NOT protected. There is nothing to gate (the source +// is CC-BY) and an unprotected URL can be handed straight to a player. Listing +// still needs a signed-in account, like everything else here. + +migrate( + (app) => { + const clips = new Collection({ + type: "base", + name: "benchmark_clips", + listRule: "@request.auth.id != ''", + viewRule: "@request.auth.id != ''", + createRule: null, + updateRule: null, + deleteRule: null, + fields: [ + // Stable identifier the app switches on; the label is what a person reads. + { type: "text", name: "key", required: true, max: 20 }, + { type: "text", name: "label", required: true, max: 40 }, + // Rungs are ordered, and the order is the answer: the highest rung that + // plays clean is what the box can do. + { type: "number", name: "rung", required: true, min: 1, onlyInt: true }, + { type: "number", name: "width", required: true, min: 1, onlyInt: true }, + { type: "number", name: "height", required: true, min: 1, onlyInt: true }, + { type: "text", name: "codec", required: true, max: 20 }, + { type: "number", name: "bitrateBps", required: true, min: 1, onlyInt: true }, + { type: "number", name: "durationS", required: true, min: 1 }, + { + type: "file", + name: "file", + required: true, + maxSelect: 1, + maxSize: 104857600, + protected: false, + }, + { type: "autodate", name: "created", onCreate: true }, + { type: "autodate", name: "updated", onCreate: true, onUpdate: true }, + ], + indexes: [ + "CREATE UNIQUE INDEX idx_benchmark_clips_key ON benchmark_clips (key)", + ], + }); + app.save(clips); + }, + (app) => { + app.delete(app.findCollectionByNameOrId("benchmark_clips")); + }, +);