123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- import { ref, watch } from "vue";
- import { generateUuid } from "@common/utils/generateUuid";
- const soundcloudDomain = "https://w.soundcloud.com";
- export const useSoundcloudPlayer = () => {
- const uuid = generateUuid();
- const soundcloudIframeElement = ref();
- const widgetId = ref();
- const volume = ref();
- const readyCallback = ref();
- const attemptsToPlay = ref(0);
- const debouncePause = ref(null);
- const iframeUrl = ref("");
- const playAttemptTimeout = ref();
- const paused = ref(true);
- const currentTrackId = ref(null);
- const methodCallbacks = {};
- const eventListenerCallbacks = {};
- const stateChangeCallbacks = [];
- const debug = (...args) =>
- process.env.NODE_ENV === "development" &&
- console.debug("[USP]", uuid, widgetId.value, ...args);
- debug("Init start");
- if (!window.soundcloudIframeLockUuids)
- window.soundcloudIframeLockUuids = new Set();
- /*
- EVENTS:
- LOAD_PROGRESS: "loadProgress",
- PLAY_PROGRESS: "playProgress",
- PLAY: "play",
- PAUSE: "pause",
- FINISH: "finish",
- SEEK: "seek",
- READY: "ready",
- OPEN_SHARE_PANEL: "sharePanelOpened",
- CLICK_DOWNLOAD: "downloadClicked",
- CLICK_BUY: "buyClicked",
- ERROR: "error"
- METHODS:
- GET_VOLUME: "getVolume",
- GET_DURATION: "getDuration",
- GET_POSITION: "getPosition",
- GET_SOUNDS: "getSounds",
- GET_CURRENT_SOUND: "getCurrentSound",
- GET_CURRENT_SOUND_INDEX: "getCurrentSoundIndex",
- IS_PAUSED: "isPaused"
- PLAY: "play",
- PAUSE: "pause",
- TOGGLE: "toggle",
- SEEK_TO: "seekTo",
- SET_VOLUME: "setVolume",
- NEXT: "next",
- PREV: "prev",
- SKIP: "skip"
- REMOVE_LISTENER: "removeEventListener",
- ADD_LISTENER: "addEventListener"
- */
- const trackState = ref("NOT_PLAYING");
- const dispatchMessage = (method, value = null) => {
- const payload = {
- method,
- value
- };
- if (!soundcloudIframeElement.value) return;
- if (
- !soundcloudIframeElement.value.src ||
- !soundcloudIframeElement.value.src.startsWith(soundcloudDomain)
- )
- return;
- if (method !== "getPosition" && method !== "getDuration")
- debug("Dispatch message", method, value);
- soundcloudIframeElement.value.contentWindow.postMessage(
- JSON.stringify(payload),
- `${soundcloudDomain}/player`
- );
- };
- const onLoadListener = () => {};
- const onMessageListener = event => {
- if (event.origin !== soundcloudDomain) return;
- const data = JSON.parse(event.data);
- if (
- data.method !== "getPosition" &&
- data.method !== "getDuration" &&
- (data.method === "ready" || data.widgetId === widgetId.value)
- )
- debug("MESSAGE DATA", data);
- if (data.method === "ready") {
- if (window.soundcloudIframeLockUuid !== uuid) return;
- widgetId.value = data.widgetId;
- if (readyCallback.value) readyCallback.value();
- if (eventListenerCallbacks[data.method])
- eventListenerCallbacks[data.method].forEach(callback => {
- callback(data.value);
- });
- window.soundcloudIframeLockUuid = null;
- document.dispatchEvent(new Event("soundcloudUnlock"));
- return;
- }
- if (data.widgetId !== widgetId.value) return;
- if (methodCallbacks[data.method]) {
- methodCallbacks[data.method].forEach(callback => {
- callback(data.value);
- });
- methodCallbacks[data.method] = [];
- }
- if (eventListenerCallbacks[data.method]) {
- eventListenerCallbacks[data.method].forEach(callback => {
- callback(data.value);
- });
- }
- };
- const addMethodCallback = (type, cb) => {
- if (!methodCallbacks[type]) methodCallbacks[type] = [];
- methodCallbacks[type].push(cb);
- };
- const changeTrackState = newTrackState => {
- clearTimeout(debouncePause.value);
- const oldTrackState = trackState.value;
- trackState.value = newTrackState;
- if (newTrackState !== oldTrackState) {
- stateChangeCallbacks.forEach(cb => {
- cb(newTrackState);
- });
- }
- };
- const soundcloudGetIsPaused = callback => {
- let called = false;
- const _callback = value => {
- if (called) return;
- called = true;
- callback(value);
- };
- addMethodCallback("isPaused", _callback);
- dispatchMessage("isPaused");
- };
- const soundcloudGetCurrentSound = callback => {
- let called = false;
- const _callback = value => {
- if (called) return;
- called = true;
- callback(value);
- };
- addMethodCallback("getCurrentSound", _callback);
- dispatchMessage("getCurrentSound");
- };
- const attemptToPlay = () => {
- if (trackState.value === "playing") return;
- if (trackState.value !== "attempting_to_play") {
- attemptsToPlay.value = 0;
- changeTrackState("attempting_to_play");
- }
- attemptsToPlay.value += 1;
- dispatchMessage("play");
- setTimeout(() => {
- soundcloudGetIsPaused(value => {
- if (trackState.value !== "attempting_to_play") return;
- // Success
- if (!value) {
- changeTrackState("playing");
- return;
- }
- soundcloudGetCurrentSound(sound => {
- // Sound is not available to play
- if (
- value &&
- !paused.value &&
- typeof sound === "object" &&
- (!sound.playable ||
- !sound.public ||
- sound.policy === "BLOCK")
- ) {
- changeTrackState("sound_unavailable");
- attemptsToPlay.value = 0;
- return;
- }
- // Too many attempts, failed
- if (attemptsToPlay.value >= 10 && value && !paused.value) {
- changeTrackState("failed_to_play");
- attemptsToPlay.value = 0;
- return;
- }
- if (playAttemptTimeout.value)
- clearTimeout(playAttemptTimeout.value);
- playAttemptTimeout.value = setTimeout(() => {
- if (trackState.value === "attempting_to_play")
- attemptToPlay();
- }, 500);
- });
- });
- }, 500);
- };
- const changeIframeUrl = url => {
- iframeUrl.value = url;
- if (url && window.soundcloudIframeLockUuid !== uuid) {
- // Don't change the iframe src if the player hasn't initialized and isn't allowed to initialize yet
- if (url) window.soundcloudIframeLockUuids.add(uuid);
- if (!window.soundcloudIframeLockUuid)
- document.dispatchEvent(new Event("soundcloudUnlock"));
- return;
- }
- if (!url) widgetId.value = null;
- soundcloudIframeElement.value.setAttribute(
- "src",
- url ?? `${soundcloudDomain}/player`
- );
- };
- const documentUnlockEventListener = () => {
- if (
- !window.soundcloudIframeLockUuid &&
- window.soundcloudIframeLockUuids.size > 0 &&
- window.soundcloudIframeLockUuids.keys().next().value === uuid
- ) {
- window.soundcloudIframeLockUuid = uuid;
- window.soundcloudIframeLockUuids.delete(uuid);
- changeIframeUrl(iframeUrl.value);
- }
- };
- watch(soundcloudIframeElement, (newElement, oldElement) => {
- if (oldElement) {
- oldElement.removeEventListener("load", onLoadListener);
- window.removeEventListener("message", onMessageListener);
- if (window.soundcloudIframeLockUuid === uuid)
- window.soundcloudIframeLockUuid = null;
- window.soundcloudIframeLockUuids.delete(uuid);
- document.removeEventListener(
- "soundcloudUnlock",
- documentUnlockEventListener
- );
- }
- if (newElement) {
- newElement.addEventListener("load", onLoadListener);
- window.addEventListener("message", onMessageListener);
- document.removeEventListener(
- "soundcloudUnlock",
- documentUnlockEventListener
- );
- document.addEventListener(
- "soundcloudUnlock",
- documentUnlockEventListener
- );
- }
- if (!window.soundcloudIframeLockUuid)
- document.dispatchEvent(new Event("soundcloudUnlock"));
- });
- /* Exported functions */
- const soundcloudPlay = () => {
- paused.value = false;
- debug("Soundcloud play");
- if (trackState.value !== "attempting_to_play") attemptToPlay();
- };
- const soundcloudPause = () => {
- paused.value = true;
- debug("Soundcloud pause");
- if (playAttemptTimeout.value) clearTimeout(playAttemptTimeout.value);
- dispatchMessage("pause");
- };
- const soundcloudSetVolume = _volume => {
- volume.value = _volume;
- debug("Soundcloud set volume");
- dispatchMessage("setVolume", _volume);
- };
- const soundcloudSeekTo = time => {
- debug("SC SEEK TO", time);
- debug("Soundcloud seek to");
- dispatchMessage("seekTo", time);
- };
- const soundcloudGetPosition = callback => {
- let called = false;
- const _callback = value => {
- if (called) return;
- called = true;
- callback(value);
- };
- addMethodCallback("getPosition", _callback);
- dispatchMessage("getPosition");
- };
- const soundcloudGetDuration = callback => {
- let called = false;
- const _callback = value => {
- if (called) return;
- called = true;
- callback(value);
- };
- addMethodCallback("getDuration", _callback);
- dispatchMessage("getDuration");
- };
- const soundcloudGetState = () => trackState.value;
- const soundcloudGetTrackId = () => currentTrackId.value;
- const soundcloudGetTrackState = () => trackState.value;
- const soundcloudLoadTrack = (trackId, startTime, _paused) => {
- if (!soundcloudIframeElement.value) return;
- debug("Soundcloud load track");
- const url = `${soundcloudDomain}/player?autoplay=false&buying=false&sharing=false&download=false&show_artwork=false&show_playcount=false&show_user=false&url=${`https://api.soundcloud.com/tracks/${trackId}`}`;
- changeIframeUrl(url);
- paused.value = _paused;
- currentTrackId.value = trackId;
- if (playAttemptTimeout.value) clearTimeout(playAttemptTimeout.value);
- changeTrackState("not_started");
- readyCallback.value = () => {
- Object.keys(eventListenerCallbacks).forEach(event => {
- dispatchMessage("addEventListener", event);
- });
- dispatchMessage("setVolume", volume.value ?? 20);
- dispatchMessage("seekTo", (startTime ?? 0) * 1000);
- if (!_paused) attemptToPlay();
- };
- };
- const soundcloudBindListener = (name, callback) => {
- if (!eventListenerCallbacks[name]) {
- eventListenerCallbacks[name] = [];
- dispatchMessage("addEventListener", name);
- }
- eventListenerCallbacks[name].push(callback);
- };
- const soundcloudOnTrackStateChange = callback => {
- debug("On track state change listener added");
- stateChangeCallbacks.push(callback);
- };
- const soundcloudDestroy = () => {
- if (!soundcloudIframeElement.value) return;
- changeIframeUrl(null);
- currentTrackId.value = null;
- changeTrackState("none");
- };
- const soundcloudUnload = () => {
- window.removeEventListener("message", onMessageListener);
- };
- soundcloudBindListener("play", () => {
- debug("On play");
- if (trackState.value !== "attempting_to_play")
- changeTrackState("playing");
- });
- soundcloudBindListener("pause", eventValue => {
- debug("On pause", eventValue);
- const finishedPlaying = eventValue.relativePosition === 1;
- if (finishedPlaying) return;
- clearTimeout(debouncePause.value);
- debouncePause.value = setTimeout(() => {
- if (trackState.value !== "attempting_to_play")
- changeTrackState("paused");
- }, 500);
- });
- soundcloudBindListener("finish", () => {
- debug("On finish");
- changeTrackState("finished");
- });
- soundcloudBindListener("error", () => {
- debug("On error");
- changeTrackState("error");
- });
- debug("Init end");
- return {
- soundcloudIframeElement,
- soundcloudPlay,
- soundcloudPause,
- soundcloudSeekTo,
- soundcloudSetVolume,
- soundcloudLoadTrack,
- soundcloudGetPosition,
- soundcloudGetDuration,
- soundcloudGetIsPaused,
- soundcloudGetState,
- soundcloudGetTrackId,
- soundcloudGetCurrentSound,
- soundcloudGetTrackState,
- soundcloudBindListener,
- soundcloudOnTrackStateChange,
- soundcloudDestroy,
- soundcloudUnload
- };
- };
|