@kieransimkin/dancemoves 3.1.19 → 3.1.20

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.mjs CHANGED
@@ -17,6 +17,7 @@ function install(__dmScope) {
17
17
  var lyricList = [];
18
18
  var lyricRenderer = null;
19
19
  var boundAudio = /* @__PURE__ */ new WeakSet();
20
+ var playbackBindings = /* @__PURE__ */ new Map();
20
21
  var activeAudio = null;
21
22
  var pageClockStarted = window2.performance && typeof window2.performance.now === "function" ? window2.performance.now() : Date.now();
22
23
  var referenceDurationSeconds = positiveNumber(rawConfig.masterDurationMilliseconds) / 1e3;
@@ -106,13 +107,16 @@ function install(__dmScope) {
106
107
  }
107
108
  function currentTick(options) {
108
109
  var settings = options || {};
109
- var audio = settings.audio || (settings.clock === "audio" ? activeAudio : null);
110
+ var audio = playbackAudio(settings.audio) || (settings.clock === "audio" ? activeAudio : null);
110
111
  if (audio && !audio.paused && !audio.ended && Number.isFinite(Number(audio.currentTime))) {
111
112
  return Number(audio.currentTime) * effectiveBpm(settings.bpm || bpm) * TICKS_PER_BEAT / 60;
112
113
  }
113
114
  var now = window2.performance && typeof window2.performance.now === "function" ? window2.performance.now() : Date.now();
114
115
  return Math.max(0, now - pageClockStarted) * effectiveBpm(settings.bpm || bpm) * TICKS_PER_BEAT / 6e4;
115
116
  }
117
+ function playbackAudio(audio) {
118
+ return audio && api && api.playbackSource ? api.playbackSource(audio) : audio;
119
+ }
116
120
  function nextIntervalTick(intervalTicks, fromTick, strictlyFuture) {
117
121
  var interval = Math.max(1, roundHalfUp(intervalTicks));
118
122
  var tick = Math.max(0, Number(fromTick) || 0);
@@ -169,7 +173,7 @@ function install(__dmScope) {
169
173
  var pollAudio = function() {
170
174
  if (cancelled)
171
175
  return;
172
- var candidate = settings.audio || activeAudio;
176
+ var candidate = playbackAudio(settings.audio) || activeAudio;
173
177
  if (!candidate || candidate.paused || candidate.ended) {
174
178
  if (sourceAudio && sourceAudio.ended) {
175
179
  cancel();
@@ -731,9 +735,18 @@ function install(__dmScope) {
731
735
  return Math.abs(duration - referenceDurationSeconds) <= Math.max(0.25, duration / 1e5);
732
736
  }
733
737
  function bindAudio(audio) {
738
+ if (api.playbackSource)
739
+ audio = api.playbackSource(audio);
734
740
  if (boundAudio.has(audio) || !matchesMasterDuration(audio))
735
741
  return;
736
742
  boundAudio.add(audio);
743
+ var removers = [];
744
+ function listen(target, name, handler) {
745
+ __dmScope.listen(target, name, handler);
746
+ removers.push(function() {
747
+ __dmScope.unlisten(target, name, handler);
748
+ });
749
+ }
737
750
  __dmScope.dataset(audio, "danceMovesTiming", "master-length");
738
751
  var state = {
739
752
  next: firstCueAfter(audio.currentTime || 0),
@@ -741,7 +754,8 @@ function install(__dmScope) {
741
754
  last: audio.currentTime || 0,
742
755
  seeking: false,
743
756
  frame: 0,
744
- pendingCue: cueAtLanding(audio.currentTime || 0)
757
+ pendingCue: cueAtLanding(audio.currentTime || 0),
758
+ endedPending: false
745
759
  };
746
760
  function stopFrame() {
747
761
  if (state.frame)
@@ -750,7 +764,7 @@ function install(__dmScope) {
750
764
  }
751
765
  function tick() {
752
766
  state.frame = 0;
753
- if (audio.paused || audio.ended || state.seeking)
767
+ if (audio.paused || audio.ended || state.seeking || document2.hidden)
754
768
  return;
755
769
  var diagnosticStartedAt = diagnosticsSink ? diagnosticNow() : 0;
756
770
  var firedCount = 0;
@@ -788,8 +802,14 @@ function install(__dmScope) {
788
802
  }
789
803
  function start() {
790
804
  stopFrame();
805
+ if (document2.hidden || audio.paused || audio.ended)
806
+ return;
791
807
  activeAudio = audio;
792
808
  state.last = positiveNumber(audio.currentTime);
809
+ if (state.endedPending) {
810
+ state.pendingCue = cueAtLanding(state.last);
811
+ state.endedPending = false;
812
+ }
793
813
  state.next = firstCueAfter(state.last);
794
814
  state.lyric = -2;
795
815
  if (state.pendingCue) {
@@ -799,47 +819,70 @@ function install(__dmScope) {
799
819
  setOwnedPlaybackRate(audio.playbackRate);
800
820
  state.frame = window2.requestAnimationFrame(tick);
801
821
  }
802
- __dmScope.listen(audio, "play", start);
803
- __dmScope.listen(audio, "playing", start);
804
- __dmScope.listen(audio, "pause", function() {
822
+ listen(audio, "play", start);
823
+ listen(audio, "playing", start);
824
+ listen(audio, "pause", function() {
805
825
  stopFrame();
806
826
  if (activeAudio === audio)
807
827
  activeAudio = null;
808
828
  if (lyricRenderer)
809
829
  lyricRenderer.hide();
810
830
  });
811
- __dmScope.listen(audio, "ended", function() {
831
+ listen(audio, "ended", function() {
812
832
  stopFrame();
813
833
  if (activeAudio === audio)
814
834
  activeAudio = null;
815
835
  state.last = 0;
816
836
  state.next = 0;
817
837
  state.lyric = -2;
818
- state.pendingCue = cueAtLanding(0);
838
+ state.pendingCue = null;
839
+ state.endedPending = true;
819
840
  if (lyricRenderer)
820
841
  lyricRenderer.hide();
821
842
  });
822
- __dmScope.listen(audio, "seeking", function() {
843
+ listen(audio, "seeking", function() {
823
844
  state.seeking = true;
824
845
  state.pendingCue = null;
825
846
  stopFrame();
826
847
  });
827
- __dmScope.listen(audio, "seeked", function() {
848
+ listen(audio, "seeked", function() {
828
849
  state.seeking = false;
829
850
  state.last = positiveNumber(audio.currentTime);
830
851
  state.next = firstCueAfter(state.last);
831
852
  state.lyric = -2;
832
853
  state.pendingCue = cueAtLanding(state.last);
854
+ playbackBindings.set(audio, function() {
855
+ stopFrame();
856
+ removers.splice(0).forEach(function(remove) {
857
+ remove();
858
+ });
859
+ boundAudio.delete(audio);
860
+ if (activeAudio === audio)
861
+ activeAudio = null;
862
+ playbackBindings.delete(audio);
863
+ });
833
864
  if (!audio.paused && !audio.ended)
834
865
  start();
835
866
  });
836
- __dmScope.listen(audio, "ratechange", function() {
867
+ listen(audio, "ratechange", function() {
837
868
  setOwnedPlaybackRate(audio.playbackRate);
838
869
  });
839
- __dmScope.listen(audio, "timeupdate", function() {
870
+ listen(audio, "timeupdate", function() {
840
871
  if (!audio.paused && !state.frame && !state.seeking)
841
872
  tick();
842
873
  });
874
+ listen(document2, "visibilitychange", function() {
875
+ if (document2.hidden) {
876
+ stopFrame();
877
+ if (lyricRenderer)
878
+ lyricRenderer.hide();
879
+ } else if (!audio.paused && !audio.ended && !state.seeking) {
880
+ state.pendingCue = null;
881
+ start();
882
+ }
883
+ });
884
+ if (!audio.paused && !audio.ended)
885
+ start();
843
886
  }
844
887
  function discoverAudio() {
845
888
  var audioElements = Array.from(document2.querySelectorAll("audio"));
@@ -953,6 +996,16 @@ function install(__dmScope) {
953
996
  registerAnimationScope,
954
997
  resetRunningAnimations,
955
998
  discoverAudio,
999
+ unregisterPlaybackSource: function(source) {
1000
+ var remove = playbackBindings.get(source);
1001
+ if (remove)
1002
+ remove();
1003
+ },
1004
+ registerPlaybackSource: function(source) {
1005
+ if (!referenceDurationSeconds)
1006
+ referenceDurationSeconds = positiveNumber(source.duration);
1007
+ bindAudio(source);
1008
+ },
956
1009
  setDiagnosticsSink,
957
1010
  diagnosticsEnabled: function() {
958
1011
  return diagnosticsAllowed && Boolean(diagnosticsSink);
@@ -993,6 +1046,10 @@ function install2(__dmScope) {
993
1046
  return document2.querySelector(value);
994
1047
  return fallback || null;
995
1048
  }
1049
+ function playbackElement(value, fallback) {
1050
+ var target = element(value, fallback);
1051
+ return target && motion.playbackSource ? motion.playbackSource(target) : target;
1052
+ }
996
1053
  function media(query) {
997
1054
  return window2.matchMedia(query);
998
1055
  }
@@ -1118,7 +1175,7 @@ function install2(__dmScope) {
1118
1175
  function playbackPulse(options) {
1119
1176
  options = options || {};
1120
1177
  var state = commonState(options, "pulse");
1121
- var audio = element(options.audio, state.root.querySelector("audio"));
1178
+ var audio = playbackElement(options.audio, state.root.querySelector("audio"));
1122
1179
  if (!audio)
1123
1180
  throw new Error("DanceMovesEffects pulse requires an audio element");
1124
1181
  var ticks = Math.max(1, Math.round(finite2(options.ticks, 16)));
@@ -1218,7 +1275,7 @@ function install2(__dmScope) {
1218
1275
  function cueTimeline(options) {
1219
1276
  options = options || {};
1220
1277
  var state = commonState(options, "cue-timeline");
1221
- var audio = element(options.audio, state.root.querySelector("audio"));
1278
+ var audio = playbackElement(options.audio, state.root.querySelector("audio"));
1222
1279
  if (!audio)
1223
1280
  throw new Error("DanceMovesEffects cueTimeline requires an audio element");
1224
1281
  var cues = (Array.isArray(options.cues) ? options.cues : []).map(function(cue, index) {
@@ -1379,7 +1436,7 @@ function install2(__dmScope) {
1379
1436
  function spritePlayback(options) {
1380
1437
  options = options || {};
1381
1438
  var state = commonState(options, "sprite-playback");
1382
- var audio = element(options.audio, state.root.querySelector("audio"));
1439
+ var audio = playbackElement(options.audio, state.root.querySelector("audio"));
1383
1440
  var target = element(options.target, state.root);
1384
1441
  if (!audio)
1385
1442
  throw new Error("DanceMovesEffects spritePlayback requires an audio element");
@@ -1573,7 +1630,7 @@ function install2(__dmScope) {
1573
1630
  function lyricStage(options) {
1574
1631
  options = options || {};
1575
1632
  var state = commonState(options, "lyric-stage");
1576
- var audio = element(options.audio, state.root.querySelector("audio"));
1633
+ var audio = playbackElement(options.audio, state.root.querySelector("audio"));
1577
1634
  var popover = element(options.popover, document2.querySelector(".dance-moves-lyric-popover"));
1578
1635
  if (!audio)
1579
1636
  throw new Error("DanceMovesEffects lyricStage requires an audio element");
@@ -4792,9 +4849,11 @@ function install9(__dmScope) {
4792
4849
  const clock = options.clock === void 0 ? "page" : options.clock;
4793
4850
  if (!["page", "audio", "auto"].includes(clock))
4794
4851
  throw new TypeError("clock must be page, audio or auto");
4795
- const audio = clock === "page" ? null : element(options.audio, "audio");
4796
- if (audio && String(audio.tagName).toLowerCase() !== "audio")
4797
- throw new TypeError("audio must be an HTMLAudioElement");
4852
+ let audio = clock === "page" ? null : element(options.audio, "audio");
4853
+ if (audio && motion.playbackSource)
4854
+ audio = motion.playbackSource(audio);
4855
+ if (audio && !["audio", "video"].includes(String(audio.tagName).toLowerCase()))
4856
+ throw new TypeError("audio must resolve to native media or a shared playback source");
4798
4857
  const rate = options.rate === void 0 ? 1 : number(options.rate, "rate");
4799
4858
  if (rate <= 0 || rate > 64)
4800
4859
  throw new RangeError("rate must be in (0, 64]");
@@ -5042,7 +5101,7 @@ function install9(__dmScope) {
5042
5101
  }
5043
5102
  if (options.resetOnCue)
5044
5103
  disposers.push(motion.onCue(typeof options.resetOnCue === "string" ? options.resetOnCue : "*", (detail) => {
5045
- if (!detail.audio || !audio || detail.audio === audio)
5104
+ if (!detail.audio || !audio || detail.audio === audio || motion.playbackSource && motion.playbackSource(detail.audio) === motion.playbackSource(audio))
5046
5105
  reset();
5047
5106
  }, { id: id + ":cue-reset" }));
5048
5107
  if (options.offscreen !== false && window2.IntersectionObserver) {
@@ -5316,6 +5375,8 @@ function createScope(root, config, { legacyGlobals = false, nonce, orientation =
5316
5375
  }
5317
5376
  const api = {
5318
5377
  id,
5378
+ root,
5379
+ legacyGlobals,
5319
5380
  window,
5320
5381
  document,
5321
5382
  get disposed() {
@@ -5918,8 +5979,428 @@ function createMotionRecorder(scope, { durationMilliseconds = 3e4, maxSamples =
5918
5979
  });
5919
5980
  }
5920
5981
 
5982
+ // .build/modules/playback.mjs
5983
+ var youtubeLoaders = /* @__PURE__ */ new WeakMap();
5984
+ function validatePlaybackMapping(input, masterDuration) {
5985
+ const offset = input.offset ?? 0, duration = input.duration ?? masterDuration, playerStart = input.playerStart ?? 0;
5986
+ for (const [key, value] of Object.entries({ offset, duration, playerStart, masterDuration })) {
5987
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || (key === "duration" || key === "masterDuration") && value === 0) throw new RangeError(`${key} must be a finite positive duration or nonnegative offset`);
5988
+ }
5989
+ if (offset + duration > masterDuration + 1e-3) throw new RangeError("The clip extends beyond its associated master");
5990
+ return Object.freeze({ offset, duration, playerStart, masterDuration });
5991
+ }
5992
+ function loadYouTubeAPI(host) {
5993
+ if (host.YT?.Player) return Promise.resolve(host.YT);
5994
+ if (youtubeLoaders.has(host)) return youtubeLoaders.get(host);
5995
+ const pending = new Promise((resolve, reject) => {
5996
+ const previous = host.onYouTubeIframeAPIReady;
5997
+ let timeout;
5998
+ const script = host.document.createElement("script");
5999
+ function finish(error) {
6000
+ host.clearTimeout(timeout);
6001
+ if (host.onYouTubeIframeAPIReady === ready) host.onYouTubeIframeAPIReady = previous;
6002
+ script.removeEventListener("error", failed);
6003
+ if (error) {
6004
+ script.remove();
6005
+ youtubeLoaders.delete(host);
6006
+ reject(error);
6007
+ } else resolve(host.YT);
6008
+ }
6009
+ function ready() {
6010
+ try {
6011
+ previous?.();
6012
+ } finally {
6013
+ finish(host.YT?.Player ? null : new Error("YouTube API unavailable"));
6014
+ }
6015
+ }
6016
+ function failed() {
6017
+ finish(new Error("YouTube API failed to load"));
6018
+ }
6019
+ host.onYouTubeIframeAPIReady = ready;
6020
+ script.src = "https://www.youtube.com/iframe_api";
6021
+ script.addEventListener("error", failed, { once: true });
6022
+ timeout = host.setTimeout(() => finish(new Error("YouTube API timed out")), 2e4);
6023
+ host.document.head.appendChild(script);
6024
+ });
6025
+ youtubeLoaders.set(host, pending);
6026
+ return pending;
6027
+ }
6028
+ function installPlayback(scope, api, host = scope.document.defaultView) {
6029
+ const groups = /* @__PURE__ */ new Map(), transports = /* @__PURE__ */ new WeakSet(), aliases = /* @__PURE__ */ new WeakMap(), controllers = /* @__PURE__ */ new Set(), registered = /* @__PURE__ */ new WeakMap();
6030
+ let disposed = false;
6031
+ function emit(target, type) {
6032
+ target.dispatchEvent(new host.Event(type));
6033
+ }
6034
+ function groupFor(reference) {
6035
+ if (disposed) throw new Error("DanceMoves playback owner is destroyed");
6036
+ const master = reference?.element || reference, declared = reference?.element ? reference.duration : void 0;
6037
+ const isNative = !!master?.addEventListener && ["AUDIO", "VIDEO"].includes(master.tagName);
6038
+ if (!isNative && (!master || typeof master.id !== "string" || !master.id.trim() || master.id.length > 160 || typeof master.duration !== "number" || !Number.isFinite(master.duration) || master.duration <= 0)) throw new TypeError("A native master or identified master duration is required");
6039
+ if (isNative && (master.ownerDocument !== host.document || !scope.legacyGlobals && !scope.root.contains(master))) throw new TypeError("The master must belong to this runtime root");
6040
+ if (groups.has(master)) {
6041
+ const group2 = groups.get(master);
6042
+ group2.setDuration(declared);
6043
+ return group2;
6044
+ }
6045
+ const transport = new host.EventTarget();
6046
+ transports.add(transport);
6047
+ let active = null, lastPaused = true, delegate = null;
6048
+ let declaredDuration = declared || 0;
6049
+ function setDuration(value) {
6050
+ if (value === void 0) return;
6051
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new RangeError("Declared master duration must be positive and finite");
6052
+ if (declaredDuration && Math.abs(declaredDuration - value) > 1e-3) throw new RangeError("Conflicting master durations");
6053
+ if (isNative && Number(master.duration) > 0 && Math.abs(Number(master.duration) - value) > 0.25) throw new RangeError("Declared master duration does not match its native reference");
6054
+ declaredDuration = value;
6055
+ }
6056
+ setDuration(declared);
6057
+ const duration = () => delegate?.duration || declaredDuration || (Number(master.duration) > 0 && Number.isFinite(Number(master.duration)) ? Number(master.duration) : Number(api.config?.masterDurationMilliseconds || 0) / 1e3);
6058
+ const target = isNative ? master : scope.root;
6059
+ const native = { element: isNative ? master : null, read: () => ({ time: Number(master.currentTime) || 0, paused: isNative ? master.paused : true, ended: master.ended || false, rate: master.playbackRate || 1, seeking: master.seeking || false, error: master.error }), pause: () => master.pause?.(), play: () => master.play?.(), seek: (time) => {
6060
+ if (isNative) master.currentTime = time;
6061
+ }, mapping: null };
6062
+ active = native;
6063
+ function read() {
6064
+ if (delegate) return { time: delegate.currentTime, paused: delegate.paused, ended: delegate.ended, rate: delegate.playbackRate, seeking: delegate.seeking, error: delegate.error };
6065
+ const raw = active.read(), map = active.mapping || { offset: 0, playerStart: 0, duration: duration() };
6066
+ const local = raw.time - map.playerStart, outside = local < -1e-3 || local > map.duration + 1e-3;
6067
+ return { ...raw, time: map.offset + Math.max(0, Math.min(map.duration, local)), paused: raw.paused || outside, ended: raw.ended || local > map.duration + 1e-3 };
6068
+ }
6069
+ Object.defineProperties(transport, {
6070
+ nodeType: { value: 1 },
6071
+ tagName: { get: () => isNative ? master.tagName : "VIDEO" },
6072
+ ownerDocument: { get: () => target.ownerDocument },
6073
+ dataset: { get: () => target.dataset },
6074
+ id: { get: () => master.id },
6075
+ isConnected: { get: () => target.isConnected },
6076
+ currentTime: { get: () => read().time, set: (value) => {
6077
+ if (delegate) {
6078
+ delegate.currentTime = value;
6079
+ return;
6080
+ }
6081
+ const map = active.mapping || { offset: 0, playerStart: 0, duration: duration() };
6082
+ active.seek(map.playerStart + Math.max(0, Math.min(map.duration, Number(value) - map.offset)));
6083
+ } },
6084
+ duration: { get: duration },
6085
+ paused: { get: () => read().paused },
6086
+ ended: { get: () => read().ended },
6087
+ seeking: { get: () => read().seeking || false },
6088
+ playbackRate: { get: () => read().rate },
6089
+ error: { get: () => read().error || null },
6090
+ element: { value: isNative ? master : null },
6091
+ activePlayer: { get: () => delegate ? delegate.activePlayer : active.element },
6092
+ masterId: { get: () => delegate ? delegate.masterId : master.currentSrc || master.src || master.id }
6093
+ });
6094
+ transport.matches = (selector) => target.matches(selector);
6095
+ transport.getAttribute = (name) => target.getAttribute(name);
6096
+ transport.setAttribute = (name, value) => target.setAttribute(name, value);
6097
+ transport.removeAttribute = (name) => target.removeAttribute(name);
6098
+ transport.play = () => delegate ? delegate.play() : active.play();
6099
+ transport.pause = () => delegate ? delegate.pause() : active.pause();
6100
+ function activate(source) {
6101
+ if (active !== source) {
6102
+ const previous = active;
6103
+ active = source;
6104
+ previous.pause();
6105
+ emit(transport, "seeking");
6106
+ emit(transport, "seeked");
6107
+ }
6108
+ }
6109
+ function forward(source, type) {
6110
+ if (type === "play" || type === "playing") activate(source);
6111
+ if (active !== source) return;
6112
+ const paused = read().paused;
6113
+ if (type === "timeupdate" && paused !== lastPaused) emit(transport, paused ? "pause" : "playing");
6114
+ lastPaused = paused;
6115
+ emit(transport, type);
6116
+ }
6117
+ const events = ["loadedmetadata", "durationchange", "play", "playing", "pause", "ended", "seeking", "seeked", "ratechange", "timeupdate", "waiting", "error"];
6118
+ const removers = [];
6119
+ function bindNative(element, mapping) {
6120
+ const source = element === master ? native : { element, mapping, read: () => ({ time: Number(element.currentTime) || 0, paused: element.paused, ended: element.ended, rate: element.playbackRate || 1, seeking: element.seeking, error: element.error }), pause: () => element.pause(), play: () => element.play(), seek: (time) => {
6121
+ element.currentTime = time;
6122
+ } };
6123
+ source.removers = [];
6124
+ for (const type of events) {
6125
+ const listener = () => forward(source, type);
6126
+ scope.listen(element, type, listener);
6127
+ const remove = () => scope.unlisten(element, type, listener);
6128
+ removers.push(remove);
6129
+ source.removers.push(remove);
6130
+ }
6131
+ return source;
6132
+ }
6133
+ if (isNative) bindNative(master, null);
6134
+ const group = {
6135
+ transport,
6136
+ duration,
6137
+ setDuration,
6138
+ activate,
6139
+ forward,
6140
+ bindNative,
6141
+ isActive: (source) => active === source,
6142
+ aliasTo(target2) {
6143
+ removers.splice(0).forEach((remove) => remove());
6144
+ delegate = target2;
6145
+ aliases.set(transport, target2);
6146
+ for (const type of events) {
6147
+ const relay = () => emit(transport, type);
6148
+ scope.listen(target2, type, relay);
6149
+ removers.push(() => scope.unlisten(target2, type, relay));
6150
+ }
6151
+ },
6152
+ reconcile(source) {
6153
+ if (active !== source) return;
6154
+ const paused = read().paused;
6155
+ if (paused !== lastPaused) {
6156
+ lastPaused = paused;
6157
+ emit(transport, paused ? "pause" : "playing");
6158
+ }
6159
+ emit(transport, "timeupdate");
6160
+ },
6161
+ remove(source) {
6162
+ if (active === source) {
6163
+ source.pause();
6164
+ active = native;
6165
+ lastPaused = true;
6166
+ emit(transport, "pause");
6167
+ emit(transport, "seeking");
6168
+ emit(transport, "seeked");
6169
+ }
6170
+ },
6171
+ destroy() {
6172
+ removers.splice(0).forEach((remove) => remove());
6173
+ emit(transport, "pause");
6174
+ }
6175
+ };
6176
+ groups.set(master, group);
6177
+ return group;
6178
+ }
6179
+ function registerPlayer(options) {
6180
+ if (!options || options.masterVerified !== true) throw new TypeError("An explicitly verified master association is required");
6181
+ const group = groupFor(options.master), mapping = validatePlaybackMapping(options, group.duration());
6182
+ const element = options.player;
6183
+ if (!element?.addEventListener || !["AUDIO", "VIDEO"].includes(element.tagName)) throw new TypeError("player must be native audio or video");
6184
+ if (element.ownerDocument !== host.document || !scope.legacyGlobals && !scope.root.contains(element)) throw new TypeError("The player must belong to this runtime root");
6185
+ if (registered.has(element)) throw new Error("Player already has a playback owner");
6186
+ if (element === options.master || element === group.transport.element) throw new TypeError("The master is already registered");
6187
+ const previousGroup = groups.get(element);
6188
+ if (previousGroup && previousGroup !== group) {
6189
+ if ([...controllers].some((owner) => owner.source === previousGroup.transport)) throw new Error("This player already owns a different master group");
6190
+ api.unregisterPlaybackSource?.(previousGroup.transport);
6191
+ previousGroup.aliasTo(group.transport);
6192
+ for (const [key, value] of groups) if (value === previousGroup) groups.set(key, group);
6193
+ }
6194
+ const source = group.bindNative(element, mapping);
6195
+ let destroyed = false;
6196
+ const controller = { source: group.transport, mapping, teardown() {
6197
+ if (destroyed) return;
6198
+ destroyed = true;
6199
+ source.removers.splice(0).forEach((remove) => remove());
6200
+ group.remove(source);
6201
+ registered.delete(element);
6202
+ controllers.delete(controller);
6203
+ }, snapshot() {
6204
+ return { kind: "native", mapping, active: group.isActive(source), destroyed };
6205
+ } };
6206
+ registered.set(element, controller);
6207
+ if (!element.paused && !element.ended) group.forward(source, "playing");
6208
+ Promise.resolve(api.ready).then(() => {
6209
+ if (!destroyed && !disposed) api.registerPlaybackSource?.(group.transport);
6210
+ });
6211
+ controllers.add(controller);
6212
+ return controller;
6213
+ }
6214
+ function registerYouTube(options) {
6215
+ if (!options || options.masterVerified !== true) throw new TypeError("An explicitly verified master association is required");
6216
+ const group = groupFor(options.master), mapping = validatePlaybackMapping(options, group.duration());
6217
+ const iframe = options.iframe;
6218
+ if (iframe?.tagName !== "IFRAME") throw new TypeError("A YouTube iframe is required");
6219
+ if (iframe.ownerDocument !== host.document || !scope.legacyGlobals && !scope.root.contains(iframe)) throw new TypeError("The iframe must belong to this runtime root");
6220
+ if (registered.has(iframe)) throw new Error("Player already has a playback owner");
6221
+ const url2 = new URL(iframe.src, host.location.href);
6222
+ if (!/^https:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[A-Za-z0-9_-]{11}(\?|$)/.test(url2.href)) throw new TypeError("Unsupported YouTube embed");
6223
+ url2.searchParams.set("enablejsapi", "1");
6224
+ url2.searchParams.set("origin", host.location.origin);
6225
+ const expectedVideoId = url2.pathname.split("/").at(-1);
6226
+ if (iframe.src !== url2.href) iframe.src = url2.href;
6227
+ let player = null, state = -1, error = null, frame = 0, destroyed = false, lastTime = null, lastWall = null, lastRate = 1, readyTimer = 0, rejectReady = null;
6228
+ const source = {
6229
+ element: iframe,
6230
+ mapping,
6231
+ read: () => ({ time: player?.getCurrentTime?.() || 0, paused: state !== 1, ended: state === 0, rate: player?.getPlaybackRate?.() || 1, error }),
6232
+ pause: () => player?.pauseVideo?.(),
6233
+ play: () => player?.playVideo?.(),
6234
+ seek: (time) => player?.seekTo?.(time, true)
6235
+ };
6236
+ function stop() {
6237
+ if (frame) scope.window.cancelAnimationFrame(frame);
6238
+ frame = 0;
6239
+ lastTime = null;
6240
+ lastWall = null;
6241
+ }
6242
+ function sample(wall) {
6243
+ frame = 0;
6244
+ if (destroyed || state !== 1 || scope.document.hidden || !group.isActive(source)) return;
6245
+ const raw = source.read();
6246
+ if (lastTime !== null && Math.abs(raw.time - lastTime - (wall - lastWall) / 1e3 * raw.rate) > 0.4) {
6247
+ group.forward(source, "seeking");
6248
+ group.forward(source, "seeked");
6249
+ }
6250
+ if (raw.rate !== lastRate) {
6251
+ lastRate = raw.rate;
6252
+ group.forward(source, "ratechange");
6253
+ }
6254
+ lastTime = raw.time;
6255
+ lastWall = wall;
6256
+ group.reconcile(source);
6257
+ frame = scope.window.requestAnimationFrame(sample);
6258
+ }
6259
+ function resume() {
6260
+ stop();
6261
+ if (!destroyed && state === 1 && !scope.document.hidden && group.isActive(source)) frame = scope.window.requestAnimationFrame(sample);
6262
+ }
6263
+ const visibility = () => {
6264
+ if (scope.document.hidden) stop();
6265
+ else {
6266
+ group.forward(source, "seeking");
6267
+ group.forward(source, "seeked");
6268
+ resume();
6269
+ }
6270
+ };
6271
+ const pageHide = () => stop(), pageShow = () => {
6272
+ if (player?.getPlayerState) state = player.getPlayerState();
6273
+ visibility();
6274
+ };
6275
+ scope.listen(scope.document, "visibilitychange", visibility);
6276
+ scope.listen(host, "pagehide", pageHide);
6277
+ scope.listen(host, "pageshow", pageShow);
6278
+ const controller = {
6279
+ source: group.transport,
6280
+ mapping,
6281
+ ready: null,
6282
+ teardown() {
6283
+ if (destroyed) return;
6284
+ destroyed = true;
6285
+ stop();
6286
+ if (readyTimer) scope.window.clearTimeout(readyTimer);
6287
+ readyTimer = 0;
6288
+ rejectReady?.(new Error("YouTube playback owner removed"));
6289
+ rejectReady = null;
6290
+ group.remove(source);
6291
+ scope.unlisten(scope.document, "visibilitychange", visibility);
6292
+ scope.unlisten(host, "pagehide", pageHide);
6293
+ scope.unlisten(host, "pageshow", pageShow);
6294
+ player?.destroy?.();
6295
+ registered.delete(iframe);
6296
+ controllers.delete(controller);
6297
+ },
6298
+ snapshot() {
6299
+ return { kind: "youtube", mapping, state, error, active: group.isActive(source), destroyed, polling: !!frame, time: group.transport.currentTime };
6300
+ }
6301
+ };
6302
+ controllers.add(controller);
6303
+ registered.set(iframe, controller);
6304
+ Promise.resolve(api.ready).then(() => {
6305
+ if (!destroyed && !disposed) api.registerPlaybackSource?.(group.transport);
6306
+ });
6307
+ controller.ready = loadYouTubeAPI(host).then((YT) => {
6308
+ if (destroyed || disposed) return controller;
6309
+ return new Promise((resolve, reject) => {
6310
+ rejectReady = reject;
6311
+ readyTimer = scope.window.setTimeout(() => {
6312
+ readyTimer = 0;
6313
+ error = new Error("YouTube player readiness timed out");
6314
+ rejectReady = null;
6315
+ reject(error);
6316
+ }, 15e3);
6317
+ player = new YT.Player(iframe, { events: {
6318
+ onReady() {
6319
+ if (destroyed) return;
6320
+ if (readyTimer) scope.window.clearTimeout(readyTimer);
6321
+ readyTimer = 0;
6322
+ rejectReady = null;
6323
+ const actual = Number(player.getDuration?.());
6324
+ if (actual > 0 && actual + 0.25 < mapping.playerStart + mapping.duration) {
6325
+ error = new Error("The mapped soundtrack window exceeds the video duration");
6326
+ reject(error);
6327
+ return;
6328
+ }
6329
+ group.forward(source, "loadedmetadata");
6330
+ const initialState = player.getPlayerState?.();
6331
+ if (initialState === 1) controllerState({ data: initialState });
6332
+ resolve(controller);
6333
+ },
6334
+ onStateChange: controllerState,
6335
+ onPlaybackRateChange() {
6336
+ if (!destroyed) group.forward(source, "ratechange");
6337
+ },
6338
+ onError(event) {
6339
+ if (destroyed) return;
6340
+ error = new Error("YouTube playback error " + event.data);
6341
+ state = -1;
6342
+ stop();
6343
+ if (readyTimer) scope.window.clearTimeout(readyTimer);
6344
+ readyTimer = 0;
6345
+ rejectReady = null;
6346
+ group.forward(source, "pause");
6347
+ group.forward(source, "error");
6348
+ reject(error);
6349
+ }
6350
+ } });
6351
+ function controllerState(event) {
6352
+ if (destroyed) return;
6353
+ state = event.data;
6354
+ stop();
6355
+ if (state === 1) {
6356
+ let actualId;
6357
+ try {
6358
+ const actualUrl = new URL(player.getVideoUrl());
6359
+ actualId = actualUrl.searchParams.get("v") || actualUrl.pathname.match(/^\/(?:shorts|embed)\/([A-Za-z0-9_-]{11})\/?$/)?.[1];
6360
+ } catch {
6361
+ }
6362
+ if (actualId !== expectedVideoId) {
6363
+ error = new Error("YouTube switched away from the verified soundtrack asset");
6364
+ state = -1;
6365
+ group.forward(source, "pause");
6366
+ group.forward(source, "error");
6367
+ return;
6368
+ }
6369
+ const actualDuration = Number(player.getDuration?.());
6370
+ if (actualDuration > 0 && actualDuration + 0.25 < mapping.playerStart + mapping.duration) {
6371
+ error = new Error("The mapped soundtrack window exceeds the video duration");
6372
+ state = -1;
6373
+ group.forward(source, "pause");
6374
+ group.forward(source, "error");
6375
+ return;
6376
+ }
6377
+ group.forward(source, "play");
6378
+ group.forward(source, "playing");
6379
+ resume();
6380
+ } else group.forward(source, state === 0 ? "ended" : state === 3 ? "waiting" : "pause");
6381
+ if (state === 3) group.forward(source, "pause");
6382
+ }
6383
+ });
6384
+ });
6385
+ controller.ready.catch(() => {
6386
+ });
6387
+ return controller;
6388
+ }
6389
+ Object.assign(api, { playbackSource: (master) => {
6390
+ let source = transports.has(master) ? master : groupFor(master).transport;
6391
+ while (aliases.has(source)) source = aliases.get(source);
6392
+ return source;
6393
+ }, registerPlayer, registerYouTube });
6394
+ scope.onDispose(() => {
6395
+ disposed = true;
6396
+ for (const controller of [...controllers]) controller.teardown();
6397
+ for (const group of new Set(groups.values())) group.destroy();
6398
+ groups.clear();
6399
+ });
6400
+ }
6401
+
5921
6402
  // .build/modules/version.mjs
5922
- var VERSION = "3.1.19";
6403
+ var VERSION = "3.1.20";
5923
6404
 
5924
6405
  // .build/modules/index.mjs
5925
6406
  var mounted = /* @__PURE__ */ new WeakMap();
@@ -5954,6 +6435,7 @@ function createDanceMoves(options = {}) {
5954
6435
  }
5955
6436
  try {
5956
6437
  runtime_default.core(scope);
6438
+ installPlayback(scope, w.DanceMoves, root.ownerDocument.defaultView);
5957
6439
  runtime_default.effects(scope);
5958
6440
  runtime_default.orientationCore(scope);
5959
6441
  runtime_default.native(scope);