@kieransimkin/dancemoves 3.1.20 → 3.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -24,6 +24,8 @@ __export(index_exports, {
24
24
  VERSION: () => VERSION,
25
25
  createDanceMoves: () => createDanceMoves,
26
26
  createMemoryPageStore: () => createMemoryPageStore,
27
+ createPlaybackPublisher: () => createPlaybackPublisher,
28
+ createPlaybackReceiver: () => createPlaybackReceiver,
27
29
  fromWordPressMeta: () => fromWordPressMeta,
28
30
  validatePageConfig: () => validatePageConfig
29
31
  });
@@ -6207,12 +6209,14 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
6207
6209
  groups.set(master, group);
6208
6210
  return group;
6209
6211
  }
6210
- function registerPlayer(options) {
6212
+ function registerEndpoint(options, provider = false) {
6211
6213
  if (!options || options.masterVerified !== true) throw new TypeError("An explicitly verified master association is required");
6212
6214
  const group = groupFor(options.master), mapping = validatePlaybackMapping(options, group.duration());
6213
6215
  const element = options.player;
6214
6216
  if (!element?.addEventListener || !["AUDIO", "VIDEO"].includes(element.tagName)) throw new TypeError("player must be native audio or video");
6215
- if (element.ownerDocument !== host.document || !scope.legacyGlobals && !scope.root.contains(element)) throw new TypeError("The player must belong to this runtime root");
6217
+ if (element.ownerDocument !== host.document || !provider && !scope.legacyGlobals && !scope.root.contains(element)) throw new TypeError("The player must belong to this runtime root");
6218
+ if (provider && (!element.removeEventListener || typeof element.play !== "function" || typeof element.pause !== "function")) throw new TypeError("An event-driven playback provider is required");
6219
+ if (provider && typeof host.Node === "function" && element instanceof host.Node) throw new TypeError("Use registerPlayer for native DOM players");
6216
6220
  if (registered.has(element)) throw new Error("Player already has a playback owner");
6217
6221
  if (element === options.master || element === group.transport.element) throw new TypeError("The master is already registered");
6218
6222
  const previousGroup = groups.get(element);
@@ -6232,7 +6236,7 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
6232
6236
  registered.delete(element);
6233
6237
  controllers.delete(controller);
6234
6238
  }, snapshot() {
6235
- return { kind: "native", mapping, active: group.isActive(source), destroyed };
6239
+ return { kind: provider ? "provider" : "native", mapping, active: group.isActive(source), destroyed };
6236
6240
  } };
6237
6241
  registered.set(element, controller);
6238
6242
  if (!element.paused && !element.ended) group.forward(source, "playing");
@@ -6242,6 +6246,8 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
6242
6246
  controllers.add(controller);
6243
6247
  return controller;
6244
6248
  }
6249
+ const registerPlayer = (options) => registerEndpoint(options);
6250
+ const registerPlaybackProvider = (options) => registerEndpoint(options, true);
6245
6251
  function registerYouTube(options) {
6246
6252
  if (!options || options.masterVerified !== true) throw new TypeError("An explicitly verified master association is required");
6247
6253
  const group = groupFor(options.master), mapping = validatePlaybackMapping(options, group.duration());
@@ -6421,7 +6427,7 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
6421
6427
  let source = transports.has(master) ? master : groupFor(master).transport;
6422
6428
  while (aliases.has(source)) source = aliases.get(source);
6423
6429
  return source;
6424
- }, registerPlayer, registerYouTube });
6430
+ }, registerPlayer, registerPlaybackProvider, registerYouTube });
6425
6431
  scope.onDispose(() => {
6426
6432
  disposed = true;
6427
6433
  for (const controller of [...controllers]) controller.teardown();
@@ -6430,8 +6436,431 @@ function installPlayback(scope, api, host = scope.document.defaultView) {
6430
6436
  });
6431
6437
  }
6432
6438
 
6439
+ // .build/modules/child-playback.mjs
6440
+ var PROTOCOL = "dancemoves.child-playback.v1";
6441
+ var EVENTS = /* @__PURE__ */ new Set(["snapshot", "loadedmetadata", "durationchange", "play", "playing", "pause", "ended", "seeking", "seeked", "ratechange", "timeupdate", "waiting", "error"]);
6442
+ var MAX_SECONDS = 86400;
6443
+ var publisherOwners = /* @__PURE__ */ new WeakMap();
6444
+ var receiverOwners = /* @__PURE__ */ new WeakMap();
6445
+ function identity(value, name) {
6446
+ if (typeof value !== "string" || !value.trim() || value.length > 200) throw new TypeError(`${name} must be a nonempty string of at most 200 characters`);
6447
+ return value;
6448
+ }
6449
+ function exactOrigin(value) {
6450
+ const url2 = new URL(value);
6451
+ if (!["http:", "https:"].includes(url2.protocol) || url2.origin !== value) throw new TypeError("An exact HTTP(S) origin is required");
6452
+ return value;
6453
+ }
6454
+ function context(options) {
6455
+ const host = options.window || globalThis.window;
6456
+ if (!host?.document || !host.crypto?.randomUUID) throw new TypeError("A browser Window with crypto.randomUUID is required");
6457
+ return { host, channel: identity(options.channel, "channel"), assetId: identity(options.assetId, "assetId"), origin: exactOrigin(options.origin) };
6458
+ }
6459
+ function validState(value) {
6460
+ return value && typeof value === "object" && typeof value.time === "number" && Number.isFinite(value.time) && value.time >= 0 && typeof value.duration === "number" && Number.isFinite(value.duration) && value.duration > 0 && value.duration <= MAX_SECONDS && value.time <= value.duration + 0.25 && typeof value.rate === "number" && Number.isFinite(value.rate) && value.rate >= 0.0625 && value.rate <= 16 && ["paused", "ended", "seeking", "buffering", "error"].every((key) => typeof value[key] === "boolean");
6461
+ }
6462
+ function packet(data, channel, assetId) {
6463
+ return data && typeof data === "object" && data.protocol === PROTOCOL && data.channel === channel && data.assetId === assetId;
6464
+ }
6465
+ function resources(host) {
6466
+ const removers = [], timers = /* @__PURE__ */ new Set();
6467
+ return {
6468
+ listen(target, type, callback) {
6469
+ target.addEventListener(type, callback);
6470
+ removers.push(() => target.removeEventListener(type, callback));
6471
+ },
6472
+ later(callback, delay) {
6473
+ const id = host.setTimeout(() => {
6474
+ timers.delete(id);
6475
+ callback();
6476
+ }, delay);
6477
+ timers.add(id);
6478
+ return id;
6479
+ },
6480
+ cancel(id) {
6481
+ if (id) {
6482
+ host.clearTimeout(id);
6483
+ timers.delete(id);
6484
+ }
6485
+ },
6486
+ dispose() {
6487
+ removers.splice(0).forEach((remove) => remove());
6488
+ for (const id of timers) host.clearTimeout(id);
6489
+ timers.clear();
6490
+ },
6491
+ snapshot() {
6492
+ return { listeners: removers.length, timers: timers.size };
6493
+ }
6494
+ };
6495
+ }
6496
+ function createPlaybackPublisher(options = {}) {
6497
+ const { host, channel, assetId, origin } = context(options), source = options.source;
6498
+ const target = options.targetWindow || host.parent;
6499
+ if (!source?.addEventListener || !source?.pause || !target?.postMessage || target === host) throw new TypeError("A playback source and distinct target Window are required");
6500
+ const owners = publisherOwners.get(host) || /* @__PURE__ */ new Map(), key = JSON.stringify([origin, channel, assetId]);
6501
+ const routes = owners.get(target) || /* @__PURE__ */ new Set();
6502
+ if (routes.has(key)) throw new Error("Child playback route already has a publisher");
6503
+ const owned = resources(host);
6504
+ let session = null, sequence = 0, commandSequence = 0, timer = 0, destroyed = false, suspended = false, lastPublished = null;
6505
+ const envelope = (type, extra = {}) => ({ protocol: PROTOCOL, channel, assetId, session, type, ...extra });
6506
+ function send(type, extra) {
6507
+ target.postMessage(envelope(type, extra), origin);
6508
+ }
6509
+ function stop() {
6510
+ owned.cancel(timer);
6511
+ timer = 0;
6512
+ }
6513
+ function sample(event = "snapshot") {
6514
+ if (event === "timeupdate" && lastPublished !== null && host.performance.now() - lastPublished < 250) return;
6515
+ stop();
6516
+ if (destroyed || suspended || !session) return;
6517
+ const state = { time: Number(source.currentTime), duration: Number(source.duration), rate: Number(source.playbackRate), paused: !!source.paused, ended: !!source.ended, seeking: !!source.seeking, buffering: event === "waiting", error: !!source.error };
6518
+ if (!validState(state)) return;
6519
+ lastPublished = host.performance.now();
6520
+ send("state", { sequence: ++sequence, event, state });
6521
+ if (!state.paused && !state.ended && !state.error && !state.buffering && !host.document.hidden) timer = owned.later(() => sample(), 250);
6522
+ }
6523
+ function disconnect(reason) {
6524
+ stop();
6525
+ source.pause();
6526
+ if (session) send("disconnect", { reason });
6527
+ session = null;
6528
+ }
6529
+ function message(event) {
6530
+ const data = event.data;
6531
+ if (destroyed || suspended || event.source !== target || event.origin !== origin || !packet(data, channel, assetId)) return;
6532
+ if (data.type === "connect" && typeof data.session === "string" && /^[0-9a-f-]{36}$/i.test(data.session)) {
6533
+ if (session !== data.session) {
6534
+ session = data.session;
6535
+ sequence = 0;
6536
+ commandSequence = 0;
6537
+ lastPublished = null;
6538
+ }
6539
+ sample();
6540
+ return;
6541
+ }
6542
+ if (data.session !== session || !session) return;
6543
+ if (data.type === "disconnect") {
6544
+ disconnect("parent-disconnected");
6545
+ return;
6546
+ }
6547
+ if (data.type !== "command" || !Number.isSafeInteger(data.sequence) || data.sequence <= commandSequence) return;
6548
+ if (!["pause", "seek", "play"].includes(data.command)) return;
6549
+ if (data.command === "seek" && (typeof data.time !== "number" || !Number.isFinite(data.time) || data.time < 0 || data.time > Number(source.duration))) return;
6550
+ commandSequence = data.sequence;
6551
+ const currentSession = session;
6552
+ Promise.resolve().then(() => {
6553
+ if (destroyed || session !== currentSession) return;
6554
+ if (data.command === "pause") source.pause();
6555
+ else if (data.command === "seek") source.currentTime = data.time;
6556
+ else if (options.allowPlayCommands === true) return source.play();
6557
+ else throw new Error("Parent play commands are disabled");
6558
+ }).then(() => {
6559
+ if (!destroyed && session === currentSession) {
6560
+ sample();
6561
+ send("ack", { sequence: data.sequence, ok: true });
6562
+ }
6563
+ }, () => {
6564
+ if (!destroyed && session === currentSession) send("ack", { sequence: data.sequence, ok: false });
6565
+ });
6566
+ }
6567
+ owned.listen(host, "message", message);
6568
+ function requestClose() {
6569
+ if (!destroyed && !suspended && session) send("dismiss", { sequence: ++sequence });
6570
+ }
6571
+ if (options.notifyEscape === true) owned.listen(host.document, "keydown", (event) => {
6572
+ if (event.key === "Escape" && !event.defaultPrevented && !host.document.fullscreenElement) requestClose();
6573
+ });
6574
+ for (const event of EVENTS) if (event !== "snapshot") owned.listen(source, event, () => sample(event));
6575
+ owned.listen(host.document, "visibilitychange", () => {
6576
+ if (host.document.hidden) {
6577
+ stop();
6578
+ if (session) {
6579
+ sample("waiting");
6580
+ stop();
6581
+ }
6582
+ } else sample();
6583
+ });
6584
+ owned.listen(host, "pagehide", () => {
6585
+ suspended = true;
6586
+ disconnect("pagehide");
6587
+ });
6588
+ owned.listen(host, "pageshow", () => {
6589
+ if (!destroyed) {
6590
+ suspended = false;
6591
+ send("available");
6592
+ }
6593
+ });
6594
+ routes.add(key);
6595
+ owners.set(target, routes);
6596
+ publisherOwners.set(host, owners);
6597
+ send("available");
6598
+ return { requestClose, teardown() {
6599
+ if (destroyed) return;
6600
+ disconnect("teardown");
6601
+ destroyed = true;
6602
+ owned.dispose();
6603
+ routes.delete(key);
6604
+ if (!routes.size) owners.delete(target);
6605
+ }, snapshot() {
6606
+ return { kind: "child-publisher", connected: !!session, destroyed, suspended, ...owned.snapshot() };
6607
+ } };
6608
+ }
6609
+ function createPlaybackReceiver(options = {}) {
6610
+ const { host, channel, assetId, origin } = context(options), iframe = options.iframe;
6611
+ if (iframe?.tagName !== "IFRAME" || !iframe.contentWindow) throw new TypeError("An attached iframe is required");
6612
+ if (receiverOwners.has(iframe)) throw new Error("The child frame already has a playback receiver");
6613
+ const staleAfter = options.staleAfterMilliseconds ?? 1500;
6614
+ if (typeof staleAfter !== "number" || !Number.isFinite(staleAfter) || staleAfter < 750 || staleAfter > 1e4) throw new RangeError("staleAfterMilliseconds must be between 750 and 10000");
6615
+ const owned = resources(host), player = new host.EventTarget(), pending = /* @__PURE__ */ new Map();
6616
+ let session = "", sequence = 0, commands = 0, lastWall = 0, timer = 0, connectTimer = 0, destroyed = false, connected = false, status = "connecting";
6617
+ let state = { time: 0, duration: 0, rate: 1, paused: true, ended: false, seeking: false, buffering: false, error: false };
6618
+ let resolveReady, rejectReady, ready, readySettled = false;
6619
+ function prepareReady() {
6620
+ readySettled = false;
6621
+ ready = new Promise((resolve, reject) => {
6622
+ resolveReady = (value) => {
6623
+ readySettled = true;
6624
+ resolve(value);
6625
+ };
6626
+ rejectReady = (error) => {
6627
+ readySettled = true;
6628
+ reject(error);
6629
+ };
6630
+ });
6631
+ ready.catch(() => {
6632
+ });
6633
+ }
6634
+ prepareReady();
6635
+ const emit = (event) => player.dispatchEvent(new host.Event(event));
6636
+ const envelope = (type, extra = {}) => ({ protocol: PROTOCOL, channel, assetId, session, type, ...extra });
6637
+ function send(type, extra) {
6638
+ iframe.contentWindow?.postMessage(envelope(type, extra), origin);
6639
+ }
6640
+ function cancelPending() {
6641
+ for (const request of pending.values()) {
6642
+ owned.cancel(request.timer);
6643
+ request.reject(new Error("Child playback disconnected"));
6644
+ }
6645
+ pending.clear();
6646
+ }
6647
+ function stop(reason) {
6648
+ owned.cancel(timer);
6649
+ owned.cancel(connectTimer);
6650
+ timer = connectTimer = 0;
6651
+ const playing = !player.paused;
6652
+ state = { ...state, time: player.currentTime, paused: true, buffering: false };
6653
+ connected = false;
6654
+ status = reason;
6655
+ cancelPending();
6656
+ if (playing) emit("pause");
6657
+ }
6658
+ function connect() {
6659
+ if (destroyed) return ready;
6660
+ if (readySettled) prepareReady();
6661
+ stop("connecting");
6662
+ session = host.crypto.randomUUID();
6663
+ sequence = 0;
6664
+ send("connect");
6665
+ const started = host.performance.now();
6666
+ const retry = () => {
6667
+ if (destroyed || connected) return;
6668
+ if (host.performance.now() - started >= 1e4) {
6669
+ send("disconnect");
6670
+ stop("unavailable");
6671
+ session = "";
6672
+ rejectReady(new Error("Child playback did not become ready"));
6673
+ return;
6674
+ }
6675
+ send("connect");
6676
+ connectTimer = owned.later(retry, 500);
6677
+ };
6678
+ connectTimer = owned.later(retry, 500);
6679
+ return ready;
6680
+ }
6681
+ function command(name, time) {
6682
+ if (destroyed || !connected) return Promise.reject(new Error("Child playback is unavailable"));
6683
+ if (pending.size >= 16) return Promise.reject(new Error("Too many pending child playback commands"));
6684
+ const id = ++commands;
6685
+ return new Promise((resolve, reject) => {
6686
+ const timeout = owned.later(() => {
6687
+ pending.delete(id);
6688
+ reject(new Error("Child playback command timed out"));
6689
+ }, 2e3);
6690
+ pending.set(id, { resolve, reject, timer: timeout });
6691
+ send("command", { sequence: id, command: name, ...name === "seek" ? { time } : {} });
6692
+ });
6693
+ }
6694
+ Object.defineProperties(player, {
6695
+ nodeType: { value: 1 },
6696
+ tagName: { value: "VIDEO" },
6697
+ ownerDocument: { value: host.document },
6698
+ isConnected: { get: () => !destroyed && iframe.isConnected },
6699
+ currentTime: { get: () => Math.min(state.duration || MAX_SECONDS, state.time + (!player.paused && !state.seeking ? Math.min(500, Math.max(0, host.performance.now() - lastWall)) / 1e3 * state.rate : 0)), set: (value) => {
6700
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > state.duration) throw new RangeError("Seek must be inside the child duration");
6701
+ command("seek", value).catch(() => {
6702
+ });
6703
+ } },
6704
+ duration: { get: () => state.duration },
6705
+ paused: { get: () => state.paused || state.buffering || state.error || !connected },
6706
+ ended: { get: () => state.ended },
6707
+ seeking: { get: () => state.seeking },
6708
+ playbackRate: { get: () => state.rate },
6709
+ error: { get: () => state.error ? new Error("Child media error") : null }
6710
+ });
6711
+ player.play = () => command("play");
6712
+ player.pause = () => {
6713
+ command("pause").catch(() => {
6714
+ });
6715
+ };
6716
+ function message(event) {
6717
+ const data = event.data;
6718
+ if (destroyed || event.source !== iframe.contentWindow || event.origin !== origin || !packet(data, channel, assetId)) return;
6719
+ if (data.type === "available") {
6720
+ if (!host.document.hidden) {
6721
+ if (connected) send("connect");
6722
+ else connect();
6723
+ }
6724
+ return;
6725
+ }
6726
+ if (data.session !== session) return;
6727
+ if (data.type === "dismiss" && Number.isSafeInteger(data.sequence) && data.sequence > sequence) {
6728
+ sequence = data.sequence;
6729
+ emit("dismiss");
6730
+ return;
6731
+ }
6732
+ if (data.type === "disconnect") {
6733
+ stop("disconnected");
6734
+ session = "";
6735
+ return;
6736
+ }
6737
+ if (data.type === "ack" && Number.isSafeInteger(data.sequence) && typeof data.ok === "boolean") {
6738
+ const request = pending.get(data.sequence);
6739
+ if (request) {
6740
+ owned.cancel(request.timer);
6741
+ pending.delete(data.sequence);
6742
+ data.ok ? request.resolve() : request.reject(new Error("Child playback rejected the command"));
6743
+ }
6744
+ return;
6745
+ }
6746
+ if (data.type !== "state" || !Number.isSafeInteger(data.sequence) || data.sequence <= sequence || !EVENTS.has(data.event) || !validState(data.state)) return;
6747
+ const previous = state, wasConnected = connected, wasPaused = player.paused;
6748
+ sequence = data.sequence;
6749
+ state = { ...data.state };
6750
+ lastWall = host.performance.now();
6751
+ connected = true;
6752
+ status = "connected";
6753
+ owned.cancel(connectTimer);
6754
+ connectTimer = 0;
6755
+ owned.cancel(timer);
6756
+ timer = 0;
6757
+ if (!wasConnected) {
6758
+ emit("loadedmetadata");
6759
+ resolveReady(controller);
6760
+ } else if (previous.duration !== state.duration) emit("durationchange");
6761
+ if (previous.rate !== state.rate) emit("ratechange");
6762
+ if (state.seeking && !previous.seeking) emit("seeking");
6763
+ if (!state.seeking && previous.seeking) emit("seeked");
6764
+ if (!state.seeking && !previous.seeking && wasConnected && Math.abs(state.time - previous.time - (wasPaused ? 0 : (lastWall - previousWall) / 1e3 * previous.rate)) > 0.4) {
6765
+ emit("seeking");
6766
+ emit("seeked");
6767
+ }
6768
+ previousWall = lastWall;
6769
+ if (wasPaused && !player.paused) {
6770
+ emit("play");
6771
+ emit("playing");
6772
+ }
6773
+ if (!wasPaused && player.paused) emit("pause");
6774
+ if (state.ended && !previous.ended) emit("ended");
6775
+ if (data.event === "waiting") emit("waiting");
6776
+ if (state.error && !previous.error) emit("error");
6777
+ emit("timeupdate");
6778
+ if (!player.paused) timer = owned.later(() => {
6779
+ send("disconnect");
6780
+ stop("stale");
6781
+ session = "";
6782
+ emit("waiting");
6783
+ }, staleAfter);
6784
+ }
6785
+ let previousWall = 0;
6786
+ owned.listen(host, "message", message);
6787
+ owned.listen(iframe, "load", connect);
6788
+ owned.listen(host, "pagehide", () => {
6789
+ send("disconnect");
6790
+ stop("suspended");
6791
+ session = "";
6792
+ });
6793
+ owned.listen(host, "pageshow", connect);
6794
+ owned.listen(host.document, "visibilitychange", () => {
6795
+ if (host.document.hidden) {
6796
+ send("command", { sequence: ++commands, command: "pause" });
6797
+ stop("hidden");
6798
+ session = "";
6799
+ } else connect();
6800
+ });
6801
+ const observer = new host.MutationObserver(() => {
6802
+ if (!iframe.isConnected) controller.teardown();
6803
+ });
6804
+ observer.observe(host.document.documentElement, { childList: true, subtree: true });
6805
+ const controller = { player, get ready() {
6806
+ return ready;
6807
+ }, reconnect: connect, teardown() {
6808
+ if (destroyed) return;
6809
+ send("command", { sequence: ++commands, command: "pause" });
6810
+ send("disconnect");
6811
+ stop("destroyed");
6812
+ destroyed = true;
6813
+ session = "";
6814
+ rejectReady(new Error("Child playback owner removed"));
6815
+ observer.disconnect();
6816
+ owned.dispose();
6817
+ receiverOwners.delete(iframe);
6818
+ }, snapshot() {
6819
+ return { kind: "child-receiver", status, connected, destroyed, time: player.currentTime, paused: player.paused, assetId, ...owned.snapshot() };
6820
+ } };
6821
+ receiverOwners.set(iframe, controller);
6822
+ connect();
6823
+ return controller;
6824
+ }
6825
+ function installChildPlayback(scope, api, host) {
6826
+ function own(controller) {
6827
+ const teardown = controller.teardown.bind(controller);
6828
+ const remove = scope.onDispose(() => teardown());
6829
+ controller.teardown = () => {
6830
+ remove();
6831
+ teardown();
6832
+ };
6833
+ return controller;
6834
+ }
6835
+ api.publishPlayback = (options) => {
6836
+ if (api.destroyed) throw new Error("DanceMoves owner is destroyed");
6837
+ return own(createPlaybackPublisher({ ...options, window: host }));
6838
+ };
6839
+ api.receiveChildPlayback = (options) => {
6840
+ if (api.destroyed) throw new Error("DanceMoves owner is destroyed");
6841
+ if (typeof api.registerPlaybackProvider !== "function") throw new Error("The shared playback provider API is required");
6842
+ if (options?.masterVerified !== true) throw new TypeError("An explicitly verified master association is required");
6843
+ const receiver = createPlaybackReceiver({ ...options, window: host });
6844
+ let registration;
6845
+ try {
6846
+ registration = api.registerPlaybackProvider({ ...options, player: receiver.player });
6847
+ } catch (error) {
6848
+ receiver.teardown();
6849
+ throw error;
6850
+ }
6851
+ return own({ source: registration.source, player: receiver.player, get ready() {
6852
+ return receiver.ready;
6853
+ }, mapping: registration.mapping, reconnect: receiver.reconnect, teardown() {
6854
+ receiver.teardown();
6855
+ registration.teardown();
6856
+ }, snapshot() {
6857
+ return { ...receiver.snapshot(), mapping: registration.mapping };
6858
+ } });
6859
+ };
6860
+ }
6861
+
6433
6862
  // .build/modules/version.mjs
6434
- var VERSION = "3.1.20";
6863
+ var VERSION = "3.1.22";
6435
6864
 
6436
6865
  // .build/modules/index.mjs
6437
6866
  var mounted = /* @__PURE__ */ new WeakMap();
@@ -6499,6 +6928,7 @@ function createDanceMoves(options = {}) {
6499
6928
  }
6500
6929
  });
6501
6930
  Object.defineProperty(api, "destroyed", { get: () => scope.disposed });
6931
+ installChildPlayback(scope, api, ownerDocument.defaultView);
6502
6932
  api.ready = Promise.resolve(api.ready).then(() => {
6503
6933
  if (!scope.disposed) scope.document.dispatchEvent(new w.CustomEvent("dance-moves-ready", {
6504
6934
  bubbles: true,
package/lib/index.d.ts CHANGED
@@ -10,6 +10,20 @@ export interface PlaybackSource extends EventTarget {
10
10
  }
11
11
  export interface PlayerRegistration extends PlaybackMapping {master:MasterReference;masterVerified:true}
12
12
  export interface PlaybackController extends Controller {source:PlaybackSource;mapping:Readonly<PlaybackMapping>;ready?:Promise<PlaybackController>}
13
+
14
+ export interface ChildPlaybackPlayer extends EventTarget {
15
+ readonly nodeType:1;readonly tagName:'VIDEO';readonly ownerDocument:Document;readonly isConnected:boolean;
16
+ currentTime:number;readonly duration:number;readonly paused:boolean;readonly ended:boolean;readonly seeking:boolean;readonly playbackRate:number;
17
+ play():Promise<void>;pause():void;
18
+ }
19
+ export interface ChildPlaybackIdentity {origin:string;channel:string;assetId:string}
20
+ export interface ChildPlaybackPublisherOptions extends ChildPlaybackIdentity {source:Pick<HTMLMediaElement,'currentTime'|'duration'|'playbackRate'|'paused'|'ended'|'seeking'|'pause'|'play'|'addEventListener'|'removeEventListener'> & {readonly error?:unknown};targetWindow?:Window;allowPlayCommands?:boolean;notifyEscape?:boolean;window?:Window}
21
+ export interface ChildPlaybackReceiverOptions extends ChildPlaybackIdentity {iframe:HTMLIFrameElement;staleAfterMilliseconds?:number;window?:Window}
22
+ export interface ChildPlaybackReceiver extends Controller {player:ChildPlaybackPlayer;readonly ready:Promise<ChildPlaybackReceiver>;reconnect():Promise<ChildPlaybackReceiver>}
23
+ export interface ChildPlaybackRegistration extends ChildPlaybackReceiverOptions, PlayerRegistration {}
24
+ export interface ChildPlaybackPublisher extends Controller {requestClose():void}
25
+ export function createPlaybackPublisher(options:ChildPlaybackPublisherOptions):ChildPlaybackPublisher;
26
+ export function createPlaybackReceiver(options:ChildPlaybackReceiverOptions):ChildPlaybackReceiver;
13
27
  export type Offset = Readonly<{x:number;y:number;z?:number}>;
14
28
  export type Metadata = string | {id?:string;handlerId?:string};
15
29
  export interface PageConfig {
@@ -58,6 +72,7 @@ export interface MotionCapture {schema:'ks-epk-motion-recording/v1';samples:Arra
58
72
  export interface DanceMoves {
59
73
  playbackSource(master:MasterReference|PlaybackSource):PlaybackSource;
60
74
  registerPlayer(options:PlayerRegistration & {player:HTMLMediaElement}):PlaybackController;
75
+ registerPlaybackProvider(options:PlayerRegistration & {player:ChildPlaybackPlayer}):PlaybackController;
61
76
  registerYouTube(options:PlayerRegistration & {iframe:HTMLIFrameElement}):PlaybackController & {ready:Promise<PlaybackController>};
62
77
  readonly version:string;readonly bpm:number;readonly bpmSource:string;readonly ticksPerBeat:16;readonly root:HTMLElement;readonly config:Readonly<PageConfig>;
63
78
  readonly destroyed:boolean;ready:Promise<DanceMoves>;
@@ -83,6 +98,8 @@ export interface DanceMoves {
83
98
  createOrientation(options:{render:(point:Offset & Record<string,unknown>)=>void;windowMilliseconds?:number;minimumSpanDegrees?:number;smoothingTimeConstantMilliseconds?:number}):OrientationController;
84
99
  connectPhoneMotion(options?:PhoneMotionOptions):PhoneMotionConnection;
85
100
  createRecorder(options?:{durationMilliseconds?:number;maxSamples?:number}):Recorder;
101
+ publishPlayback(options:ChildPlaybackPublisherOptions):ChildPlaybackPublisher;
102
+ receiveChildPlayback(options:ChildPlaybackRegistration):ChildPlaybackReceiver & {source:EventTarget & {currentTime:number;readonly paused:boolean};mapping:Readonly<{offset?:number;duration?:number;playerStart?:number}>};
86
103
  on(type:string,callback:EventListener):Remove;resources():{id:string;disposed:boolean;listeners:number;timers:number;frames:number;observers:number;created:number};destroy():void;
87
104
  }
88
105
  export const VERSION:string;