diff --git a/src/index.all.js b/src/index.all.js index b17324d50..5ebcb71a4 100644 --- a/src/index.all.js +++ b/src/index.all.js @@ -16,6 +16,7 @@ export * from './plugins/playlist/playlist.js'; export * from './plugins/interaction-areas/interaction-areas.service.js'; export * from './plugins/visual-search/visual-search.js'; export * from './plugins/srt-text-tracks/srt-text-tracks.js'; +export * from './plugins/share/share.js'; export * from './components/shoppable-bar/shoppable-widget.js'; export * from './components/recommendations-overlay/recommendations-overlay.js'; diff --git a/src/plugins/playlist/playlist.js b/src/plugins/playlist/playlist.js index 9afd60d23..5b79a7703 100644 --- a/src/plugins/playlist/playlist.js +++ b/src/plugins/playlist/playlist.js @@ -1,4 +1,3 @@ -import isPlainObject from 'lodash/isPlainObject'; import { sliceProperties } from 'utils/slicing'; import { PLAYER_EVENT } from 'utils/consts'; import { getCloudinaryUrl } from 'plugins/cloudinary/common'; @@ -22,7 +21,7 @@ const playlist = (player, options = {}) => { playlistWidget.dispose(); } - if (isPlainObject(options.playlistWidget)) { + if (options.playlistWidget?.show != false) { if (player.fluid_) { options.playlistWidget.fluid = true; } diff --git a/src/plugins/playlist/ui/playlist.js b/src/plugins/playlist/ui/playlist.js index 8f7157130..d48aa6b84 100644 --- a/src/plugins/playlist/ui/playlist.js +++ b/src/plugins/playlist/ui/playlist.js @@ -240,7 +240,13 @@ class Playlist { current.recommendations(this._defaultRecommendationsResolver(current)); } - this._context.source(current, { recommendationOptions: { disableAutoShow: true, itemBuilder } }); + // Extract source options for analytics + const sourceOptions = current.getInitOptions ? current.getInitOptions() : {}; + + this._context.source(current, { + ...sourceOptions, + recommendationOptions: { disableAutoShow: true, itemBuilder } + }); const eventData = { playlist: this, current, next: this.next() }; diff --git a/src/utils/get-analytics-player-options.js b/src/utils/get-analytics-player-options.js index 35c51a447..ee9b4ecbc 100644 --- a/src/utils/get-analytics-player-options.js +++ b/src/utils/get-analytics-player-options.js @@ -11,14 +11,35 @@ const filterDefaultsAndNulls = (obj) => Object.entries(obj).reduce((filtered, [k return filtered; }, {}); -const getCloudinaryOptions = (cloudinaryOptions = {}) => ({ - autoShowRecommendations: cloudinaryOptions.autoShowRecommendations, - fontFace: cloudinaryOptions.fontFace, - posterOptions: hasConfig(cloudinaryOptions.posterOptions), - posterOptionsPublicId: cloudinaryOptions.posterOptions && hasConfig(cloudinaryOptions.posterOptions.publicId) +const getSourceOptions = (sourceOptions = {}) => ({ + posterOptions: hasConfig(sourceOptions.posterOptions), + posterOptionsPublicId: sourceOptions.posterOptions && hasConfig(sourceOptions.posterOptions.publicId), + autoShowRecommendations: sourceOptions.autoShowRecommendations, + fontFace: sourceOptions.fontFace, + sourceTypes: sourceOptions.sourceTypes, + chapters: (() => { + if (sourceOptions.chapters === true) return 'auto'; + if (sourceOptions.chapters && sourceOptions.chapters.url) return 'url'; + if (sourceOptions.chapters) return 'inline-chapters'; + return undefined; + })(), + visualSearch: sourceOptions.visualSearch, + download: hasConfig(sourceOptions.download), + recommendations: sourceOptions.recommendations && sourceOptions.recommendations.length, + ...(hasConfig(sourceOptions.adaptiveStreaming) ? { + abrStrategy: sourceOptions?.adaptiveStreaming?.strategy === defaults.adaptiveStreaming.strategy ? undefined : sourceOptions?.adaptiveStreaming?.strategy, + } : {}), + shoppable: hasConfig(sourceOptions.shoppable), + shoppableProductsLength: sourceOptions.shoppable && sourceOptions.shoppable.products && sourceOptions.shoppable.products.length, + ...(sourceOptions.title || sourceOptions.description || sourceOptions.info ? { + sourceInfo: hasConfig(sourceOptions.info), + sourceTitle: (typeof sourceOptions.title === 'string' ? sourceOptions.title : sourceOptions.info?.title), + sourceDescription: (typeof sourceOptions.description === 'string' ? sourceOptions.description : sourceOptions.info?.subtitle || sourceOptions.info?.description) + } : {}), + ...(hasConfig(sourceOptions.textTracks) ? getTextTracksOptions(sourceOptions.textTracks) : {}) }); -const getTranscriptOptions = (textTracks = {}) => { +const getTextTracksOptions = (textTracks = {}) => { const tracksArr = [textTracks.captions, ...(textTracks.subtitles || [])]; return { textTracks: hasConfig(textTracks), @@ -30,40 +51,19 @@ const getTranscriptOptions = (textTracks = {}) => { transcriptAutoLoaded: tracksArr.some((track) => !track.url) || null, transcriptFromURl: tracksArr.some((track) => track.url?.endsWith('.transcript')) || null, vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null, - srtFromUrl: tracksArr.some((track) => track.url?.endsWith('.srt')) || null + srtFromUrl: tracksArr.some((track) => track.url?.endsWith('.srt')) || null, + ...(textTracks.options ? { + styledTextTracksTheme: textTracks.options.theme, + styledTextTracksFont: textTracks.options.fontFace, + styledTextTracksFontSize: textTracks.options.fontSize, + styledTextTracksGravity: textTracks.options.gravity, + styledTextTracksBox: hasConfig(textTracks.options.box), + styledTextTracksStyle: hasConfig(textTracks.options.style), + styledTextTracksWordHighlightStyle: hasConfig(textTracks.options.wordHighlightStyle) + } : {}) }; }; -const getSourceOptions = (sourceOptions = {}) => ({ - sourceTypes: sourceOptions.sourceTypes, - chapters: sourceOptions.chapters && (sourceOptions.chapters.url ? 'url' : 'inline-chapters'), - visualSearch: hasConfig(sourceOptions.visualSearch), - download: hasConfig(sourceOptions.download), - recommendations: sourceOptions.recommendations && sourceOptions.recommendations.length, - ...(sourceOptions.adaptiveStreaming ? { - abrStrategy: sourceOptions?.adaptiveStreaming?.strategy, - } : {}), - shoppable: hasConfig(sourceOptions.shoppable), - shoppableProductsLength: sourceOptions.shoppable && sourceOptions.shoppable.products && sourceOptions.shoppable.products.length, - ...(sourceOptions.title || sourceOptions.description || sourceOptions.info ? { - sourceInfo: hasConfig(sourceOptions.info), - sourceTitle: (typeof sourceOptions.title === 'string' ? sourceOptions.title : sourceOptions.info?.title), - sourceDescription: (typeof sourceOptions.description === 'string' ? sourceOptions.description : sourceOptions.info?.subtitle || sourceOptions.info?.description) - } : {}), - ...(sourceOptions.textTracks ? { - ...(hasConfig(sourceOptions.textTracks) && getTranscriptOptions(sourceOptions.textTracks)), - ...(sourceOptions.textTracks.options ? { - styledTextTracksTheme: sourceOptions.textTracks.options.theme, - styledTextTracksFont: sourceOptions.textTracks.options.fontFace, - styledTextTracksFontSize: sourceOptions.textTracks.options.fontSize, - styledTextTracksGravity: sourceOptions.textTracks.options.gravity, - styledTextTracksBox: hasConfig(sourceOptions.textTracks.options.box), - styledTextTracksStyle: hasConfig(sourceOptions.textTracks.options.style), - styledTextTracksWordHighlightStyle: hasConfig(sourceOptions.textTracks.options.wordHighlightStyle) - } : {}) - } : {}) -}); - const getAdsOptions = (adsOptions = {}) => ({ adsAdTagUrl: adsOptions.adTagUrl, adsShowCountdown: adsOptions.showCountdown, @@ -74,7 +74,9 @@ const getAdsOptions = (adsOptions = {}) => ({ adsAdsInPlaylist: adsOptions.adsInPlaylist }); -const getPlaylistWidgetOptions = (playlistWidgetOptions = {}) => ({ +const getPlaylistOptions = (playlistWidgetOptions = {}) => ({ + playlist: playlistWidgetOptions.playlist, + playlistByTag: playlistWidgetOptions.playlistByTag, playlistWidgetDirection: playlistWidgetOptions.direction, playlistWidgetTotal: playlistWidgetOptions.total }); @@ -119,8 +121,7 @@ export const getAnalyticsFromPlayerOptions = (playerOptions) => filterDefaultsAn colors: playerOptions.colors && JSON.stringify(playerOptions.colors), controlBar: (JSON.stringify(playerOptions.controlBar) !== JSON.stringify(defaults.controlBar)) && JSON.stringify(playerOptions.controlBar), - ...getCloudinaryOptions(playerOptions.cloudinary), - ...getSourceOptions(playerOptions.sourceOptions), + ...getSourceOptions(playerOptions.sourceOptions || {}), ...getAdsOptions(playerOptions.ads), - ...getPlaylistWidgetOptions(playerOptions.playlistWidget) + ...getPlaylistOptions(playerOptions.playlistWidget) }); diff --git a/src/video-player.js b/src/video-player.js index 61c633d3a..74af93058 100644 --- a/src/video-player.js +++ b/src/video-player.js @@ -24,7 +24,6 @@ import { PLAYER_EVENT, SOURCE_TYPE } from './utils/consts'; import { getAnalyticsFromPlayerOptions } from './utils/get-analytics-player-options'; import { extendCloudinaryConfig, normalizeOptions, isRawUrl, ERROR_CODE } from './plugins/cloudinary/common'; import { isVideoInReadyState, checkIfVideoIsAvailable } from './utils/video-retry'; -import { SOURCE_PARAMS } from './video-player.const'; const INTERNAL_ANALYTICS_URL = 'https://analytics-api-s.cloudinary.com'; @@ -117,7 +116,7 @@ class VideoPlayer extends Utils.mixin(Eventable) { const baseParams = new URLSearchParams({ vpVersion: VERSION, vpInstanceId: this.getVPInstanceId(), - cloudName: options.cloudinary.cloudinaryConfig.cloud_name, + cloudName: options.cloudinary.cloud_name, ...internalAnalyticsMetadata, }).toString(); fetch(`${INTERNAL_ANALYTICS_URL}/video_player_source?${analyticsParams}&${baseParams}`); @@ -410,14 +409,20 @@ class VideoPlayer extends Utils.mixin(Eventable) { } _initCloudinary() { - const { cloudinaryConfig } = this.playerOptions.cloudinary; + const cloudinaryConfig = this.playerOptions.cloudinary; cloudinaryConfig.chainTarget = this; if (cloudinaryConfig.secure !== false) { extendCloudinaryConfig(cloudinaryConfig, { secure: true }); } - this.videojs.cloudinary(this.playerOptions.cloudinary); + // Merge cloudinary config with source config for the plugin + const cloudinaryOptions = { + cloudinaryConfig, + ...this.playerOptions.sourceOptions + }; + + this.videojs.cloudinary(cloudinaryOptions); } _initAnalytics() { @@ -516,11 +521,11 @@ class VideoPlayer extends Utils.mixin(Eventable) { this._setExtendedEvents(); // Load first video (mainly to support video tag 'source' and 'public-id' attributes) - // Source parameters are set to playerOptions.cloudinary - const source = this.playerOptions.cloudinary.source || this.playerOptions.cloudinary.publicId; + // Source parameters are set to playerOptions.sourceOptions + const source = this.playerOptions.sourceOptions.source || this.playerOptions.sourceOptions.publicId; if (source) { - const sourceOptions = Object.assign({}, this.playerOptions.cloudinary); + const sourceOptions = Object.assign({}, this.playerOptions.sourceOptions); this.source(source, sourceOptions); } @@ -597,10 +602,8 @@ class VideoPlayer extends Utils.mixin(Eventable) { } // Inherit source parameters from player options (source options take precedence) - const inherited = pick(this.playerOptions, SOURCE_PARAMS); - const inheritedNested = this.playerOptions.cloudinary ? pick(this.playerOptions.cloudinary, SOURCE_PARAMS) : {}; - - options = { ...inherited, ...inheritedNested, ...options }; + const inherited = this.playerOptions.sourceOptions || {}; + options = { ...inherited, ...options }; if (options.shoppable && this.videojs.shoppable) { this.videojs.shoppable(this.videojs, options); @@ -629,6 +632,8 @@ class VideoPlayer extends Utils.mixin(Eventable) { } playlist(sources, options = {}) { + this.playerOptions.playlistWidget = { ...(this.playerOptions.playlistWidget || { show: false }), playlist: true }; + options = Object.assign({}, options, { playlistWidget: this.playerOptions.playlistWidget }); this.videojs.one(PLAYER_EVENT.READY, async () => { @@ -640,6 +645,8 @@ class VideoPlayer extends Utils.mixin(Eventable) { } playlistByTag(tag, options = {}) { + this.playerOptions.playlistWidget = { ...(this.playerOptions.playlistWidget || { show: false}), playlistByTag: true }; + options = Object.assign({}, options, { playlistWidget: this.playerOptions.playlistWidget }); return new Promise((resolve) => { diff --git a/src/video-player.utils.js b/src/video-player.utils.js index 4c49934e6..187f54755 100644 --- a/src/video-player.utils.js +++ b/src/video-player.utils.js @@ -4,6 +4,7 @@ import defaults from './config/defaults'; import { PLAYER_PARAMS, SOURCE_PARAMS, + CLOUDINARY_CONFIG_PARAM, FLUID_CLASS_NAME, AUTO_PLAY_MODE } from './video-player.const'; @@ -89,8 +90,17 @@ export const extractOptions = (elem, options) => { // VideoPlayer specific options const playerOptions = Utils.sliceAndUnsetProperties(options, ...PLAYER_PARAMS); - // Cloudinary plugin specific options - playerOptions.cloudinary = Utils.sliceAndUnsetProperties(playerOptions, ...SOURCE_PARAMS); + // Cloudinary SDK config (cloud_name, secure, etc.) + playerOptions.cloudinary = Utils.sliceAndUnsetProperties(playerOptions, ...CLOUDINARY_CONFIG_PARAM); + + // Merge with cloudinaryConfig from src/index.js (e.g., secureDistribution -> secure_distribution) + if (playerOptions.cloudinaryConfig) { + Object.assign(playerOptions.cloudinary, playerOptions.cloudinaryConfig); + delete playerOptions.cloudinaryConfig; + } + + // Source-level config (visualSearch, chapters, etc.) + playerOptions.sourceOptions = Utils.sliceAndUnsetProperties(playerOptions, ...SOURCE_PARAMS); // Allow explicitly passing options to videojs using the `videojs` namespace, in order // to avoid param name conflicts: