Type in your question below and we'll check to see what answers we can find...
Loading article...
Submitting...
If you couldn't find any answers in the previous step then we need to post your question in the community and wait for someone to respond. You'll be notified when that happens.
Simply add some detail to your question and refine the title if needed, choose the relevant category, then post.
Before we can post your question we need you to quickly make an account (or sign in if you already have one).
Don't worry - it's quick and painless! Just click below, and once you're logged in we'll bring you right back here and post your question. We'll remember what you've already typed in so you won't have to do it again.
Plan
Free
Country
US
Device
Macbook Pro
Operating System
MacOS Sonoma
My Question or Issue
I'm using a stimulus controller to initialize the iframe embed. I'm adding an event listener to the EmbedController that works fine and I want to remove it when the form is selected. I'm not sure what I'm doing wrong but the removeListener function is not working for the EmbedController. I've gone into debugger, the textArea event is being triggered and I have access to EmbedController.removeListener but it returns undefined when I call
in the debugger. I can see the event listed under _listeners on EmbedController. I'm not sure where to go from here. My code is below. Any help is appreciated🙂
Solved! Go to Solution.
It feels a bit janky but I got it working by redefining EmbedController._listeners['playback_update'] = []
So my working code is:
const callback = (EmbedController) => {
const playBackPositionBroadcast = (playbackEvent) => {
/// broadcast to dom
};
const removePlaybackListener = () => {
EmbedController._listeners['playback_update'] = [];
};
const addPlaybackListener = () => {
EmbedController.addListener('playback_update', playBackPositionBroadcast);
};
this.textAreaTarget.addEventListener('focus', removePlaybackListener);
this.textAreaTarget.addEventListener('blur', addPlaybackListener);
EmbedController.addListener('playback_update', playBackPositionBroadcast);
};
Howldy ZachAttax!
It seems there might be an issue with scoping or context when trying to remove the listener. In your code, you're using an arrow function for the click event listener, which might affect the scope of this. Also, the this keyword might refer to the global context rather than the expected context within the callback.
Try capturing the reference to playBackPositionBroadcast outside the callback function, and make sure to use a named function for the click event listener to ensure proper scoping:
const callback = (EmbedController) => {
const playBackPositionBroadcast = (playbackEvent) => {
// code to send playbackEvent.data.position to form in the dom
};
const removePlaybackListener = () => {
EmbedController.removeListener('playback_update', playBackPositionBroadcast);
this.textAreaTarget.removeEventListener('click', removePlaybackListener);
};
EmbedController.addListener('playback_update', playBackPositionBroadcast);
this.textAreaTarget.addEventListener('click', removePlaybackListener);
};
By defining a named function (removePlaybackListener), it ensures that the same reference is used for adding and removing the event listener. Also, using removeEventListener instead of removeListener for the textAreaTarget should properly remove the click event listener when triggered.
Bark some more orders at me if this doesn't solve the issue and we'll get it sorted,
-Prague the Dog
I think you're right that it is a context issue. I tried your suggestion of using named function for the event callback. I also changed the event from to 'click' to 'blur' and 'focus'. Using the debugger I'm getting the expected context of this as my stimulus controller. I can also see that the 'click' and 'blur' events are being triggered correctly but the EmbedController events are still not being removed. Here is my updated code.
const callback = (EmbedController) => {
const playBackPositionBroadcast = (playbackEvent) => {
// broadcast code
};
const removePlaybackListener = () => {
EmbedController.removeListener('playback_update', playBackPositionBroadcast);
};
const addPlaybackListener = () => {
EmbedController.addListener('playback_update', playBackPositionBroadcast);
};
EmbedController.addListener('playback_update', playBackPositionBroadcast);
this.textAreaTarget.addEventListener('focus', removePlaybackListener);
this.textAreaTarget.addEventListener('blur', addPlaybackListener);
};
Sniffing around this a bit more it seems like despite correctly setting up the event listeners for 'blur' and 'focus' on this.textAreaTarget, the issue might persist due to the EmbedController's removeListener and addListener methods not functioning as expected.
Ensure the event names are accurate and the EmbedController methods are being used correctly. Debugging with console logs or a debugger might help trace the execution flow within the EmbedController.
Consider checking if the removeListener and addListener methods are implemented as intended in the EmbedController. Sometimes, specific nuances or requirements might exist regarding event handling within that library or framework.
Getting closer?
Top paw regards,
-Prague the Dog
This is from the docs
playback_update
This event fires after the state of playback changes. Playback state changes can occur by the user tapping on the playback controls in the Embed, or programmatically through methods of the iFrame API, such as the seek() method.
The event handler will receive an object with four properties describing the current playback state: isPaused (bool), isBuffering (bool), duration (number) and position (number). duration describes the length of the loaded podcast episode in miliseconds and position describes the cursor location in miliseconds.
Code sample
EmbedController.addListener('playback_update', e => {
document.getElementById('progressTimestamp').innerText = `${parseInt(e.data.position / 1000, 10)} s`;
});
I'm not seeing any documentation for removeListener but it is in the source code on line 48
this.removeListener = (eventName, handler) => {
if (!this._listeners[eventName] || !this._listeners[eventName].length) {
this._listeners[eventName] = this._listeners[eventName].filter(storedHandler => handler !== storedHandler);
}
};
I'm not sure how I could debug the EmbedController since it's part of the Spotify API
full source code
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @spotify-internal/uri */ "./node_modules/@spotify-internal/uri/lib/index.js");
/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ "./src/iframe-api/src/v1/constants.ts");
/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./types */ "./src/iframe-api/src/v1/types.ts");
let SpotifyIframeApi;
class SpotifyEmbedController {
constructor(targetElement, options) {
var _a, _b, _c, _d, _e;
this._listeners = {
[_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.READY]: [],
[_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.PLAYBACK_UPDATE]: [],
[_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR]: [],
};
this.currentUri = '';
this.loading = false;
this._commandQ = [];
this.setIframeDimensions = (width, height) => {
this.iframeElement.setAttribute('width', width);
this.iframeElement.setAttribute('height', height);
};
this.onWindowMessages = (e) => {
var _a, _b, _c, _d, _e;
if (e.source === this.iframeElement.contentWindow) {
if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === _types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.READY) {
this.onPlayerReady();
}
if (((_b = e.data) === null || _b === void 0 ? void 0 : _b.type) === _types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.PLAYBACK_UPDATE) {
const playbackState = (_c = e.data) === null || _c === void 0 ? void 0 : _c.payload;
this.onPlaybackUpdate(playbackState);
}
if (((_d = e.data) === null || _d === void 0 ? void 0 : _d.type) === _types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR) {
this.emitEvent(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR, (_e = e.data) === null || _e === void 0 ? void 0 : _e.payload);
}
}
};
this.addListener = (eventName, handler) => {
if (!this._listeners[eventName]) {
this._listeners[eventName] = [];
}
this._listeners[eventName].push(handler);
return () => {
this.removeListener(eventName, handler);
};
};
this.removeListener = (eventName, handler) => {
if (!this._listeners[eventName] || !this._listeners[eventName].length) {
this._listeners[eventName] = this._listeners[eventName].filter(storedHandler => handler !== storedHandler);
}
};
this.on = this.addListener;
this.off = this.removeListener;
this.once = (eventName, handler) => {
this.addListener(eventName, (...args) => {
handler(...args);
this.removeListener(eventName, handler);
});
};
this.emitEvent = (eventName, eventData) => {
var _a;
(_a = this._listeners[eventName]) === null || _a === void 0 ? void 0 : _a.forEach(handler => handler({ data: eventData }));
};
this.onPlayerReady = () => {
this.loading = false;
this.flushCommandQ();
this.playerReadyAck();
this.emitEvent(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.READY, {});
};
this.onPlaybackUpdate = (playbackState) => {
this.emitEvent(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.PLAYBACK_UPDATE, playbackState);
};
this.loadUri = (uriString, preferVideo = false, timestampInSeconds = 0) => {
var _a;
if (uriString !== this.currentUri) {
const uri = (0,_spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.parseURI)(uriString);
if (uri &&
(uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.EPISODE ||
uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.SHOW ||
uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.ALBUM ||
uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.ARTIST ||
uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.PLAYLIST_V2 ||
uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.TRACK)) {
this.loading = true;
const type = uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.PLAYLIST_V2 ? 'playlist' : uri.type;
const embedURL = new URL(`${this.host}/embed/${type}/${uri.id}`);
if (this.options.startAt || timestampInSeconds) {
const startAt = timestampInSeconds !== null && timestampInSeconds !== void 0 ? timestampInSeconds : parseInt((_a = this.options.startAt) !== null && _a !== void 0 ? _a : '0', 10);
if (!isNaN(startAt))
embedURL.searchParams.append('t', startAt.toString());
}
if (this.options.theme === 'dark') {
embedURL.searchParams.append('theme', '0');
}
if (this.queryParamReferer) {
embedURL.searchParams.append('referrer', this.queryParamReferer);
}
this.iframeElement.src=embedURL.href;
if ((uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.EPISODE || uri.type === _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__.URITypeMap.SHOW) &&
preferVideo) {
SpotifyIframeApi.supportsVideo(uriString).then(isVideoContent => {
if (isVideoContent) {
embedURL.pathname += '/video';
this.iframeElement.src=embedURL.href;
}
});
}
}
else {
const error = {
code: _types__WEBPACK_IMPORTED_MODULE_2__.EmbedErrorCode.InvalidURI,
message: `Invalid URI: ${uriString}`,
recoverable: false,
};
this.emitEvent(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR, error);
}
}
};
this.playerReadyAck = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.LOAD_COMPLETE_ACK });
};
this.play = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.PLAY });
};
this.playFromStart = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.PLAY_FROM_START });
};
this.pause = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.PAUSE });
};
this.resume = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.RESUME });
};
this.togglePlay = () => {
this.sendMessageToEmbed({ command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.TOGGLE_PLAY });
};
this.seek = (timestampInSeconds) => {
this.sendMessageToEmbed({
command: _types__WEBPACK_IMPORTED_MODULE_2__.IframeCommands.SEEK,
timestamp: timestampInSeconds,
});
};
this.sendMessageToEmbed = (messageToSend) => {
if (this.loading) {
this._commandQ.push(messageToSend);
return;
}
if (this.iframeElement.contentWindow) {
this.iframeElement.contentWindow.postMessage(messageToSend, '*');
}
else {
console.error(`Spotify Embed: Failed to send message ${messageToSend}.
Most likely this is because the iframe containing the embed player
has not finished loading yet.`);
}
};
this.flushCommandQ = () => {
if (this._commandQ.length) {
this._commandQ.forEach(command => {
setTimeout(() => {
this.sendMessageToEmbed(command);
}, 0);
});
this._commandQ = [];
}
};
this.destroy = () => {
var _a;
(_a = this.iframeElement.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(this.iframeElement);
window.removeEventListener('message', this.onWindowMessages);
};
this.host =
((_a = window.SpotifyIframeConfig) === null || _a === void 0 ? void 0 : _a.host) ||
'https://open.spotify.com';
this.queryParamReferer =
((_b = window.SpotifyIframeConfig) === null || _b === void 0 ? void 0 : _b.referrer) ||
undefined;
this.options = options;
const url = new URL(this.host);
if (!url.hostname.endsWith('.spotify.com') &&
!url.hostname.endsWith('.spotify.net')) {
throw new Error(`It appears that the hostname for the Spotify embed player has been overridden.
Please make sure that "SpotifyEmbedConfig" is not being overridden.`);
}
const iframeElement = document.createElement('iframe');
Object.entries(_constants__WEBPACK_IMPORTED_MODULE_1__.EMBED_REQUIRED_IFRAME_ATTRIBUTES).forEach(([attr, val]) => {
const htmlAttr = attr.toLowerCase();
if (typeof val === 'boolean') {
iframeElement.setAttribute(htmlAttr, '');
}
else {
iframeElement.setAttribute(htmlAttr, val);
}
});
this.iframeElement = iframeElement;
const width = (_c = options.width) !== null && _c !== void 0 ? _c : '100%';
const height = (_d = options.height) !== null && _d !== void 0 ? _d : _constants__WEBPACK_IMPORTED_MODULE_1__.EMBED_DEFAULT_HEIGHT.toString();
this.setIframeDimensions(width, height);
(_e = targetElement.parentElement) === null || _e === void 0 ? void 0 : _e.replaceChild(iframeElement, targetElement);
window.addEventListener('message', this.onWindowMessages);
}
}
SpotifyIframeApi = {
createController: (targetElement, options = {}, callbackOrEventsHandlers) => {
var _a, _b, _c;
const apiInstance = new SpotifyEmbedController(targetElement, options);
let callback;
let errorCallback = undefined;
if (typeof callbackOrEventsHandlers === 'function') {
callback = callbackOrEventsHandlers;
if (options.uri) {
apiInstance.loadUri(options.uri, options.preferVideo);
}
}
else {
callback = callbackOrEventsHandlers.onCreateCallback;
if ((_a = callbackOrEventsHandlers === null || callbackOrEventsHandlers === void 0 ? void 0 : callbackOrEventsHandlers.events) === null || _a === void 0 ? void 0 : _a.onError) {
errorCallback = callbackOrEventsHandlers.events.onError;
apiInstance.addListener(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR, errorCallback);
}
if ((_b = callbackOrEventsHandlers === null || callbackOrEventsHandlers === void 0 ? void 0 : callbackOrEventsHandlers.events) === null || _b === void 0 ? void 0 : _b.onReady) {
apiInstance.addListener(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.READY, callbackOrEventsHandlers.events.onReady);
}
if ((_c = callbackOrEventsHandlers === null || callbackOrEventsHandlers === void 0 ? void 0 : callbackOrEventsHandlers.events) === null || _c === void 0 ? void 0 : _c.onPlaybackUpdate) {
apiInstance.addListener(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.PLAYBACK_UPDATE, callbackOrEventsHandlers.events.onPlaybackUpdate);
}
}
if (options.uri) {
if (!errorCallback) {
const errorHandler = error => {
throw new Error(error.data.message);
};
apiInstance.addListener(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR, errorHandler);
apiInstance.loadUri(options.uri, options.preferVideo);
apiInstance.removeListener(_types__WEBPACK_IMPORTED_MODULE_2__.IframeAPIEvent.ERROR, errorHandler);
}
else {
apiInstance.loadUri(options.uri, options.preferVideo);
}
}
if (callback)
callback(apiInstance);
},
supportsVideo: async (uri) => {
var _a;
const host = (_a = window.SpotifyIframeConfig) === null || _a === void 0 ? void 0 : _a.host;
const response = await fetch(`${host}/oembed?url=${encodeURIComponent(uri)}`, {
method: 'GET',
});
const data = await response.json();
return data.type === 'video';
},
};
/* harmony default export */ __webpack_exports__["default"] = (SpotifyIframeApi);
if (!window.onSpotifyIframeApiReady) {
console.warn(`SpotifyIframeApi: "onSpotifyIframeApiReady" has not been defined.
Please read the docs to see why you are seeing this warning.
https://developer.spotify.com/documentation/embeds/references/iframe-api`);
}
else {
window.onSpotifyIframeApiReady(SpotifyIframeApi);
}
//# sourceURL=webpack://embed-standalone-prod/./src/iframe-api/src/v1/index.ts?
Woof what a line of code... lol
The code you shared involves an SpotifyEmbedController managing Spotify iframe API events. The addListener and removeListener methods handle event addition and removal. Despite correct usage, the removeListener for 'playback_update' might not be working as expected. It's challenging to debug or check the source code of the Spotify API directly. Double-checking event names and their consistency could be helpful in troubleshooting.
It feels a bit janky but I got it working by redefining EmbedController._listeners['playback_update'] = []
So my working code is:
const callback = (EmbedController) => {
const playBackPositionBroadcast = (playbackEvent) => {
/// broadcast to dom
};
const removePlaybackListener = () => {
EmbedController._listeners['playback_update'] = [];
};
const addPlaybackListener = () => {
EmbedController.addListener('playback_update', playBackPositionBroadcast);
};
this.textAreaTarget.addEventListener('focus', removePlaybackListener);
this.textAreaTarget.addEventListener('blur', addPlaybackListener);
EmbedController.addListener('playback_update', playBackPositionBroadcast);
};
Pawesome dude!
It's great to hear that you were able to make it work! Adjusting EmbedController._listeners['playback_update'] to an empty array is a workaround solution if removeListener doesn't behave as expected. While it may solve the immediate issue, directly manipulating private properties (such as _listeners) might not be the recommended approach due to potential risks related to API changes or unintended consequences.
HMU if you need me to fetch anything else,
-Prague the Dog
Hey there you, Yeah, you! 😁 Welcome - we're glad you joined the Spotify Community! While you here, let's have a fun game and get…