chart-preview 1.0.3 → 1.2.0
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/CHANGELOG.md +12 -0
- package/README.md +34 -4
- package/dist/index.d.ts +82 -4
- package/dist/index.js +266 -21
- package/dist/index.mjs +265 -21
- package/package.json +1 -1
- package/src/ChartPreview.ts +324 -8
- package/src/ChartPreviewPlayer.ts +65 -12
- package/src/SngLoader.ts +27 -3
- package/src/index.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var src_exports = {};
|
|
|
31
31
|
__export(src_exports, {
|
|
32
32
|
ChartPreview: () => ChartPreview,
|
|
33
33
|
ChartPreviewPlayer: () => ChartPreviewPlayer,
|
|
34
|
+
areAnimationsSupported: () => areAnimationsSupported,
|
|
34
35
|
extractSngFile: () => extractSngFile,
|
|
35
36
|
fetchSngFile: () => fetchSngFile,
|
|
36
37
|
findAudioFiles: () => findAudioFiles,
|
|
@@ -74,6 +75,196 @@ function isAudioContextSupported() {
|
|
|
74
75
|
var HIGHWAY_DURATION_MS = 1500;
|
|
75
76
|
var SCALE = 0.105;
|
|
76
77
|
var NOTE_SPAN_WIDTH = 0.95;
|
|
78
|
+
function isImageDecoderSupported() {
|
|
79
|
+
return typeof ImageDecoder !== "undefined";
|
|
80
|
+
}
|
|
81
|
+
function areAnimationsSupported() {
|
|
82
|
+
return isImageDecoderSupported();
|
|
83
|
+
}
|
|
84
|
+
var AnimatedTexture = class {
|
|
85
|
+
constructor(width, height) {
|
|
86
|
+
this.frameIndex = 0;
|
|
87
|
+
this.frameCount = 0;
|
|
88
|
+
this.lastFrameTime = 0;
|
|
89
|
+
this.frameDurations = [];
|
|
90
|
+
/** Pre-decoded frames stored as ImageBitmap for fast synchronous access */
|
|
91
|
+
this.frameCache = [];
|
|
92
|
+
this.isAnimated = false;
|
|
93
|
+
this.disposed = false;
|
|
94
|
+
this.canvas = document.createElement("canvas");
|
|
95
|
+
this.canvas.width = width;
|
|
96
|
+
this.canvas.height = height;
|
|
97
|
+
this.ctx = this.canvas.getContext("2d");
|
|
98
|
+
this.texture = new THREE.CanvasTexture(this.canvas);
|
|
99
|
+
this.texture.colorSpace = THREE.SRGBColorSpace;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates an AnimatedTexture from a URL.
|
|
103
|
+
* Uses ImageDecoder for animation if available, otherwise loads as static texture.
|
|
104
|
+
* Pre-decodes all frames during initialization for optimal render performance.
|
|
105
|
+
*/
|
|
106
|
+
static async create(url) {
|
|
107
|
+
if (!isImageDecoderSupported()) {
|
|
108
|
+
return loadStaticTexture(url);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(url);
|
|
112
|
+
if (!response.ok || !response.body) {
|
|
113
|
+
throw new Error(`Failed to fetch ${url}`);
|
|
114
|
+
}
|
|
115
|
+
const contentType = response.headers.get("content-type") || "image/webp";
|
|
116
|
+
const isSupported = await ImageDecoder.isTypeSupported(contentType);
|
|
117
|
+
if (!isSupported) {
|
|
118
|
+
return loadStaticTexture(url);
|
|
119
|
+
}
|
|
120
|
+
const decoder = new ImageDecoder({
|
|
121
|
+
data: response.body,
|
|
122
|
+
type: contentType
|
|
123
|
+
});
|
|
124
|
+
await decoder.completed;
|
|
125
|
+
const track = decoder.tracks.selectedTrack;
|
|
126
|
+
if (!track) {
|
|
127
|
+
throw new Error("No track found in image");
|
|
128
|
+
}
|
|
129
|
+
const frameCount = track.frameCount;
|
|
130
|
+
if (frameCount <= 1) {
|
|
131
|
+
const result = await decoder.decode({ frameIndex: 0 });
|
|
132
|
+
const frame = result.image;
|
|
133
|
+
const animTexture2 = new AnimatedTexture(
|
|
134
|
+
frame.displayWidth,
|
|
135
|
+
frame.displayHeight
|
|
136
|
+
);
|
|
137
|
+
animTexture2.ctx.drawImage(frame, 0, 0);
|
|
138
|
+
animTexture2.texture.needsUpdate = true;
|
|
139
|
+
frame.close();
|
|
140
|
+
decoder.close();
|
|
141
|
+
return animTexture2.texture;
|
|
142
|
+
}
|
|
143
|
+
const firstResult = await decoder.decode({ frameIndex: 0 });
|
|
144
|
+
const firstFrame = firstResult.image;
|
|
145
|
+
const animTexture = new AnimatedTexture(
|
|
146
|
+
firstFrame.displayWidth,
|
|
147
|
+
firstFrame.displayHeight
|
|
148
|
+
);
|
|
149
|
+
animTexture.frameDurations = [];
|
|
150
|
+
animTexture.frameCache = [];
|
|
151
|
+
for (let i = 0; i < frameCount; i++) {
|
|
152
|
+
try {
|
|
153
|
+
const frameResult = await decoder.decode({ frameIndex: i });
|
|
154
|
+
const videoFrame = frameResult.image;
|
|
155
|
+
const bitmap = await createImageBitmap(videoFrame);
|
|
156
|
+
animTexture.frameCache.push(bitmap);
|
|
157
|
+
const durationMs = (videoFrame.duration ?? 1e5) / 1e3;
|
|
158
|
+
animTexture.frameDurations.push(durationMs);
|
|
159
|
+
videoFrame.close();
|
|
160
|
+
} catch {
|
|
161
|
+
animTexture.frameDurations.push(100);
|
|
162
|
+
if (animTexture.frameCache.length > 0) {
|
|
163
|
+
animTexture.frameCache.push(
|
|
164
|
+
animTexture.frameCache[animTexture.frameCache.length - 1]
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (animTexture.frameCache.length > 0) {
|
|
170
|
+
animTexture.ctx.drawImage(animTexture.frameCache[0], 0, 0);
|
|
171
|
+
animTexture.texture.needsUpdate = true;
|
|
172
|
+
}
|
|
173
|
+
animTexture.frameCount = animTexture.frameCache.length;
|
|
174
|
+
animTexture.isAnimated = animTexture.frameCount > 1;
|
|
175
|
+
animTexture.lastFrameTime = performance.now();
|
|
176
|
+
firstFrame.close();
|
|
177
|
+
decoder.close();
|
|
178
|
+
return animTexture;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.warn(
|
|
181
|
+
"Failed to load animated texture, falling back to static:",
|
|
182
|
+
error
|
|
183
|
+
);
|
|
184
|
+
return loadStaticTexture(url);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Updates the texture to the current animation frame based on elapsed time.
|
|
189
|
+
* This method is SYNCHRONOUS for optimal render performance - all frames
|
|
190
|
+
* are pre-decoded during initialization.
|
|
191
|
+
*/
|
|
192
|
+
tick() {
|
|
193
|
+
if (!this.isAnimated || this.disposed || this.frameCache.length === 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const now = performance.now();
|
|
197
|
+
const elapsed = now - this.lastFrameTime;
|
|
198
|
+
const currentFrameDuration = this.frameDurations[this.frameIndex] || 100;
|
|
199
|
+
if (elapsed >= currentFrameDuration) {
|
|
200
|
+
this.frameIndex = (this.frameIndex + 1) % this.frameCount;
|
|
201
|
+
this.lastFrameTime = now;
|
|
202
|
+
const frame = this.frameCache[this.frameIndex];
|
|
203
|
+
if (frame) {
|
|
204
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
205
|
+
this.ctx.drawImage(frame, 0, 0);
|
|
206
|
+
this.texture.needsUpdate = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Disposes of resources used by this animated texture.
|
|
212
|
+
*/
|
|
213
|
+
dispose() {
|
|
214
|
+
this.disposed = true;
|
|
215
|
+
for (const bitmap of this.frameCache) {
|
|
216
|
+
bitmap.close();
|
|
217
|
+
}
|
|
218
|
+
this.frameCache = [];
|
|
219
|
+
this.texture.dispose();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
async function loadStaticTexture(url) {
|
|
223
|
+
const textureLoader = new THREE.TextureLoader();
|
|
224
|
+
try {
|
|
225
|
+
const texture = await textureLoader.loadAsync(url);
|
|
226
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
227
|
+
return texture;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.warn(`Failed to load static texture from ${url}:`, error);
|
|
230
|
+
return createPlaceholderTexture();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function createPlaceholderTexture() {
|
|
234
|
+
const canvas = document.createElement("canvas");
|
|
235
|
+
canvas.width = 32;
|
|
236
|
+
canvas.height = 32;
|
|
237
|
+
const ctx = canvas.getContext("2d");
|
|
238
|
+
ctx.fillStyle = "#FF00FF";
|
|
239
|
+
ctx.fillRect(0, 0, 32, 32);
|
|
240
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
241
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
242
|
+
return texture;
|
|
243
|
+
}
|
|
244
|
+
var AnimatedTextureManager = class {
|
|
245
|
+
constructor() {
|
|
246
|
+
this.animatedTextures = [];
|
|
247
|
+
}
|
|
248
|
+
register(texture) {
|
|
249
|
+
if (texture instanceof AnimatedTexture) {
|
|
250
|
+
this.animatedTextures.push(texture);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Updates all animated textures. This is now SYNCHRONOUS for optimal performance.
|
|
255
|
+
*/
|
|
256
|
+
tick() {
|
|
257
|
+
for (const texture of this.animatedTextures) {
|
|
258
|
+
texture.tick();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
dispose() {
|
|
262
|
+
for (const texture of this.animatedTextures) {
|
|
263
|
+
texture.dispose();
|
|
264
|
+
}
|
|
265
|
+
this.animatedTextures = [];
|
|
266
|
+
}
|
|
267
|
+
};
|
|
77
268
|
var BARRE1_TYPE = 99991;
|
|
78
269
|
var BARRE2_TYPE = 99992;
|
|
79
270
|
var BARRE3_TYPE = 99993;
|
|
@@ -145,6 +336,7 @@ var _ChartPreview = class {
|
|
|
145
336
|
const chartPreview = new _ChartPreview();
|
|
146
337
|
chartPreview.instrumentType = (0, import_scan_chart.getInstrumentType)(config.instrument);
|
|
147
338
|
chartPreview.highwayTexture = config.textures.highwayTexture;
|
|
339
|
+
chartPreview.animatedTextureManager = config.textures.animatedTextureManager;
|
|
148
340
|
chartPreview.progressIntervalMs = config.progressIntervalMs ?? DEFAULT_PROGRESS_INTERVAL_MS;
|
|
149
341
|
chartPreview.camera = new ChartCamera(config.container);
|
|
150
342
|
chartPreview.renderer = new ChartRenderer(config.container);
|
|
@@ -229,6 +421,7 @@ var _ChartPreview = class {
|
|
|
229
421
|
this.renderer.dispose();
|
|
230
422
|
this.renderer.forceContextLoss();
|
|
231
423
|
this.audioManager.closeAudio();
|
|
424
|
+
this.animatedTextureManager.dispose();
|
|
232
425
|
}
|
|
233
426
|
addHighwayToScene(highwayTexture) {
|
|
234
427
|
const mat = new THREE.MeshBasicMaterial({ map: highwayTexture });
|
|
@@ -264,6 +457,7 @@ var _ChartPreview = class {
|
|
|
264
457
|
this.notesManager.updateDisplayedNotes(
|
|
265
458
|
this.audioManager.chartCurrentTimeMs
|
|
266
459
|
);
|
|
460
|
+
this.animatedTextureManager.tick();
|
|
267
461
|
const scrollPosition = -0.9 * (this.audioManager.chartCurrentTimeMs / 1e3) * (HIGHWAY_DURATION_MS / 1e3);
|
|
268
462
|
this.highwayTexture.offset.y = -1 * scrollPosition;
|
|
269
463
|
this.renderer.render(this.scene, this.camera);
|
|
@@ -665,11 +859,16 @@ var NotesManager = class {
|
|
|
665
859
|
}
|
|
666
860
|
for (let i = maxNoteEventIndex + 1; this.noteEvents[i] && this.noteEvents[i].msTime < renderEndTimeMs; i++) {
|
|
667
861
|
const note = this.noteEvents[i];
|
|
862
|
+
const material = this.noteMaterials.get(note.type)?.get(note.flags);
|
|
863
|
+
if (!material) {
|
|
864
|
+
console.warn(
|
|
865
|
+
`Missing texture for note type ${note.type} with flags ${note.flags}`
|
|
866
|
+
);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
668
869
|
const noteGroup = new THREE.Group();
|
|
669
870
|
const scale = note.type === import_scan_chart.noteTypes.kick ? 0.045 : note.type === import_scan_chart.noteTypes.open && this.instrumentType === import_scan_chart.instrumentTypes.sixFret ? 0.04 : SCALE;
|
|
670
|
-
const sprite = new THREE.Sprite(
|
|
671
|
-
this.noteMaterials.get(note.type).get(note.flags)
|
|
672
|
-
);
|
|
871
|
+
const sprite = new THREE.Sprite(material);
|
|
673
872
|
noteGroup.add(sprite);
|
|
674
873
|
sprite.center = new THREE.Vector2(
|
|
675
874
|
note.type === import_scan_chart.noteTypes.kick ? 0.62 : 0.5,
|
|
@@ -746,9 +945,23 @@ var EventSequence = class {
|
|
|
746
945
|
return earliestActiveEventIndex === null ? this.lastPrecedingEventIndex + 1 : earliestActiveEventIndex;
|
|
747
946
|
}
|
|
748
947
|
};
|
|
749
|
-
async function loadTextures(instrumentType) {
|
|
948
|
+
async function loadTextures(instrumentType, options = {}) {
|
|
949
|
+
const { animationsEnabled = true } = options;
|
|
750
950
|
const textureLoader = new THREE.TextureLoader();
|
|
951
|
+
const animatedTextureManager = new AnimatedTextureManager();
|
|
751
952
|
const load = (path) => textureLoader.loadAsync("https://static.enchor.us/" + path);
|
|
953
|
+
const loadAnimated = async (path) => {
|
|
954
|
+
const url = `https://static.enchor.us/preview-${path}.webp`;
|
|
955
|
+
if (!animationsEnabled) {
|
|
956
|
+
return loadStaticTexture(url);
|
|
957
|
+
}
|
|
958
|
+
const result = await AnimatedTexture.create(url);
|
|
959
|
+
if (result instanceof AnimatedTexture) {
|
|
960
|
+
animatedTextureManager.register(result);
|
|
961
|
+
return result.texture;
|
|
962
|
+
}
|
|
963
|
+
return result;
|
|
964
|
+
};
|
|
752
965
|
const [highwayTexture, strikelineTexture, noteTextures] = await Promise.all([
|
|
753
966
|
(async () => {
|
|
754
967
|
const texture = await load("preview-highway.png");
|
|
@@ -770,9 +983,7 @@ async function loadTextures(instrumentType) {
|
|
|
770
983
|
(async () => {
|
|
771
984
|
const texturePromises = [];
|
|
772
985
|
const addTexture = (type, flags, path) => {
|
|
773
|
-
const texture =
|
|
774
|
-
`https://static.enchor.us/preview-${path}.webp`
|
|
775
|
-
);
|
|
986
|
+
const texture = loadAnimated(path);
|
|
776
987
|
texturePromises.push({ type, flags, texture });
|
|
777
988
|
return texture;
|
|
778
989
|
};
|
|
@@ -937,7 +1148,8 @@ async function loadTextures(instrumentType) {
|
|
|
937
1148
|
return {
|
|
938
1149
|
highwayTexture,
|
|
939
1150
|
strikelineTexture,
|
|
940
|
-
noteTextures
|
|
1151
|
+
noteTextures,
|
|
1152
|
+
animatedTextureManager
|
|
941
1153
|
};
|
|
942
1154
|
}
|
|
943
1155
|
function adjustParsedChart(parsedChart, instrument, difficulty) {
|
|
@@ -1184,7 +1396,7 @@ function isVideoFile(fileName) {
|
|
|
1184
1396
|
const ext = fileName.toLowerCase().slice(fileName.lastIndexOf("."));
|
|
1185
1397
|
return videoExtensions.includes(ext);
|
|
1186
1398
|
}
|
|
1187
|
-
async function prepareChartData(files, instrument, difficulty, initialSeekPercent = 0) {
|
|
1399
|
+
async function prepareChartData(files, instrument, difficulty, initialSeekPercent = 0, textureOptions = {}) {
|
|
1188
1400
|
const filteredFiles = files.filter((f) => !isVideoFile(f.fileName));
|
|
1189
1401
|
const scannedChart = (0, import_scan_chart2.scanChartFolder)(filteredFiles);
|
|
1190
1402
|
if (!scannedChart.playable || !scannedChart.notesData) {
|
|
@@ -1219,7 +1431,10 @@ async function prepareChartData(files, instrument, difficulty, initialSeekPercen
|
|
|
1219
1431
|
}
|
|
1220
1432
|
const audioFiles = findAudioFiles(filteredFiles);
|
|
1221
1433
|
const instrumentType = (0, import_scan_chart2.getInstrumentType)(instrument);
|
|
1222
|
-
const textures = await ChartPreview.loadTextures(
|
|
1434
|
+
const textures = await ChartPreview.loadTextures(
|
|
1435
|
+
instrumentType,
|
|
1436
|
+
textureOptions
|
|
1437
|
+
);
|
|
1223
1438
|
const lastNote = parsedChart.trackData.flatMap((t) => t.noteEventGroups.flat()).reduce((max, note) => Math.max(max, note.msTime + note.msLength), 0);
|
|
1224
1439
|
const audioLengthMs = scannedChart.song_length || lastNote + 5e3;
|
|
1225
1440
|
const startDelayMs = (scannedChart.delay || 0) + (scannedChart.chart_offset || 0) * 1e3;
|
|
@@ -1540,10 +1755,11 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1540
1755
|
}
|
|
1541
1756
|
};
|
|
1542
1757
|
this.handleFullscreenChange = () => {
|
|
1543
|
-
|
|
1758
|
+
const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement;
|
|
1759
|
+
this._isFullscreen = fullscreenElement !== null && fullscreenElement !== void 0;
|
|
1544
1760
|
this.container.classList.toggle("fullscreen", this._isFullscreen);
|
|
1545
1761
|
this.fullscreenBtn.innerHTML = this._isFullscreen ? ICONS.fullscreenExit : ICONS.fullscreen;
|
|
1546
|
-
this.fullscreenBtn.title = this._isFullscreen ? "Exit fullscreen" : "
|
|
1762
|
+
this.fullscreenBtn.title = this._isFullscreen ? "Exit fullscreen" : "Enter fullscreen";
|
|
1547
1763
|
};
|
|
1548
1764
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
1549
1765
|
this.render();
|
|
@@ -1568,6 +1784,10 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1568
1784
|
connectedCallback() {
|
|
1569
1785
|
this.setAttribute("tabindex", "0");
|
|
1570
1786
|
document.addEventListener("fullscreenchange", this.handleFullscreenChange);
|
|
1787
|
+
document.addEventListener(
|
|
1788
|
+
"webkitfullscreenchange",
|
|
1789
|
+
this.handleFullscreenChange
|
|
1790
|
+
);
|
|
1571
1791
|
}
|
|
1572
1792
|
disconnectedCallback() {
|
|
1573
1793
|
this.dispose();
|
|
@@ -1575,6 +1795,10 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1575
1795
|
"fullscreenchange",
|
|
1576
1796
|
this.handleFullscreenChange
|
|
1577
1797
|
);
|
|
1798
|
+
document.removeEventListener(
|
|
1799
|
+
"webkitfullscreenchange",
|
|
1800
|
+
this.handleFullscreenChange
|
|
1801
|
+
);
|
|
1578
1802
|
this.removeEventListener("keydown", this.handleKeydown);
|
|
1579
1803
|
}
|
|
1580
1804
|
render() {
|
|
@@ -1655,7 +1879,8 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1655
1879
|
this.onSeekEnd();
|
|
1656
1880
|
}
|
|
1657
1881
|
});
|
|
1658
|
-
this.fullscreenBtn.addEventListener("click", () => {
|
|
1882
|
+
this.fullscreenBtn.addEventListener("click", (e) => {
|
|
1883
|
+
e.stopPropagation();
|
|
1659
1884
|
this.toggleFullscreen();
|
|
1660
1885
|
});
|
|
1661
1886
|
this.addEventListener("keydown", this.handleKeydown);
|
|
@@ -1968,7 +2193,8 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1968
2193
|
files,
|
|
1969
2194
|
config.instrument,
|
|
1970
2195
|
config.difficulty,
|
|
1971
|
-
config.initialSeekPercent
|
|
2196
|
+
config.initialSeekPercent,
|
|
2197
|
+
{ animationsEnabled: config.animationsEnabled }
|
|
1972
2198
|
);
|
|
1973
2199
|
await this.loadChart({
|
|
1974
2200
|
parsedChart: preparedData.parsedChart,
|
|
@@ -2010,7 +2236,8 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
2010
2236
|
files,
|
|
2011
2237
|
config.instrument,
|
|
2012
2238
|
config.difficulty,
|
|
2013
|
-
config.initialSeekPercent
|
|
2239
|
+
config.initialSeekPercent,
|
|
2240
|
+
{ animationsEnabled: config.animationsEnabled }
|
|
2014
2241
|
);
|
|
2015
2242
|
await this.loadChart({
|
|
2016
2243
|
parsedChart: preparedData.parsedChart,
|
|
@@ -2049,7 +2276,8 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
2049
2276
|
config.files,
|
|
2050
2277
|
config.instrument,
|
|
2051
2278
|
config.difficulty,
|
|
2052
|
-
config.initialSeekPercent
|
|
2279
|
+
config.initialSeekPercent,
|
|
2280
|
+
{ animationsEnabled: config.animationsEnabled }
|
|
2053
2281
|
);
|
|
2054
2282
|
await this.loadChart({
|
|
2055
2283
|
parsedChart: preparedData.parsedChart,
|
|
@@ -2149,12 +2377,28 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
2149
2377
|
* Toggle fullscreen mode
|
|
2150
2378
|
*/
|
|
2151
2379
|
async toggleFullscreen() {
|
|
2152
|
-
|
|
2380
|
+
const doc = document;
|
|
2381
|
+
const container = this.container;
|
|
2382
|
+
const isFullscreenEnabled = doc.fullscreenEnabled || doc.webkitFullscreenEnabled;
|
|
2383
|
+
if (!isFullscreenEnabled)
|
|
2153
2384
|
return;
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2385
|
+
const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement;
|
|
2386
|
+
try {
|
|
2387
|
+
if (fullscreenElement) {
|
|
2388
|
+
if (doc.exitFullscreen) {
|
|
2389
|
+
await doc.exitFullscreen();
|
|
2390
|
+
} else if (doc.webkitExitFullscreen) {
|
|
2391
|
+
doc.webkitExitFullscreen();
|
|
2392
|
+
}
|
|
2393
|
+
} else {
|
|
2394
|
+
if (container.requestFullscreen) {
|
|
2395
|
+
await container.requestFullscreen();
|
|
2396
|
+
} else if (container.webkitRequestFullscreen) {
|
|
2397
|
+
container.webkitRequestFullscreen();
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
console.warn("Fullscreen toggle failed:", error);
|
|
2158
2402
|
}
|
|
2159
2403
|
}
|
|
2160
2404
|
/**
|
|
@@ -2187,6 +2431,7 @@ var import_scan_chart3 = require("scan-chart");
|
|
|
2187
2431
|
0 && (module.exports = {
|
|
2188
2432
|
ChartPreview,
|
|
2189
2433
|
ChartPreviewPlayer,
|
|
2434
|
+
areAnimationsSupported,
|
|
2190
2435
|
extractSngFile,
|
|
2191
2436
|
fetchSngFile,
|
|
2192
2437
|
findAudioFiles,
|