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__);
var _spotify_internal_uri__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "./node_modules/@spotify-internal/uri/lib/index.js");
var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__( "./src/iframe-api/src/v1/constants.ts");
var _types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__( "./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';
},
};
__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);
}