← All changes
|
Modules/Integrations/FluentPlayer/Http/Controllers/MediaController.php
+341
-38
2.6.01
→
2.10.01
View file →
| @@ -2,8 +2,9 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace FluentCommunity\Modules\Integrations\FluentPlayer\Http\Controllers; |
| 4 | 4 | |
| 5 | 5 | use FluentCommunity\App\Http\Controllers\Controller; |
| 6 | +use FluentCommunity\App\Models\Feed; | |
| 6 | 7 | use FluentCommunity\App\Models\Media; |
| 7 | 8 | use FluentCommunity\App\Services\Helper; |
| 8 | 9 | use FluentCommunity\App\Services\Libs\FileSystem; |
| 9 | 10 | use FluentCommunity\Framework\Http\Request\Request; |
| @@ -42,16 +43,21 @@ | ||
| 42 | 43 | 'message' => __('You do not have permission to upload videos', 'fluent-community') |
| 43 | 44 | ]); |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | - $allowedVideoTypes = apply_filters('fluent_community/support_video_types', [ | |
| 47 | - 'video/mp4', | |
| 48 | - 'video/m3u8', | |
| 49 | - 'video/mpd', | |
| 50 | - 'video/webm', | |
| 51 | - 'video/mov', | |
| 52 | - 'video/quicktime' | |
| 53 | - ]); | |
| 47 | + $hasAudioUpload = Arr::get($fluentPlayerSettings, 'enable_audio') === 'yes'; | |
| 48 | + $mediaKind = $request->getSafe('media_kind', 'sanitize_text_field', ''); | |
| 49 | + if (!in_array($mediaKind, ['video', 'audio'], true)) { | |
| 50 | + $mediaKind = null; | |
| 51 | + } | |
| 52 | + | |
| 53 | + if ($mediaKind === 'audio' && !$hasAudioUpload) { | |
| 54 | + return $this->sendError([ | |
| 55 | + 'message' => __('Uploading audio files is not allowed', 'fluent-community') | |
| 56 | + ]); | |
| 57 | + } | |
| 58 | + | |
| 59 | + $allowedMediaTypes = Bootstrap::getAllowedMediaTypes($fluentPlayerSettings, $mediaKind); | |
| 54 | 60 | $maxFileUnit = apply_filters('fluent_community/video_upload_max_file_unit', 'MB'); |
| 55 | 61 | $maxFileSize = apply_filters('fluent_community/video_upload_max_file_size', 300); |
| 56 | 62 | $allowedFileSize = $maxFileSize; |
| 57 | 63 | if (strtoupper($maxFileUnit) == 'MB') { |
| @@ -59,14 +65,14 @@ | ||
| 59 | 65 | } else if (strtoupper($maxFileUnit) == 'GB') { |
| 60 | 66 | $allowedFileSize = $maxFileSize * 1024 * 1024; |
| 61 | 67 | } |
| 62 | 68 | |
| 63 | - $allowedTypesString = implode(',', $allowedVideoTypes); | |
| 69 | + $allowedTypesString = implode(',', $allowedMediaTypes); | |
| 64 | 70 | |
| 65 | 71 | $files = $this->validate($request->files(), [ |
| 66 | 72 | 'file' => 'mimetypes:' . $allowedTypesString . '|max:' . $allowedFileSize, |
| 67 | 73 | ], [ |
| 68 | - 'file.mimetypes' => __('The file must be a valid video type (MP4, M3U8, MPD, WebM, MOV).', 'fluent-community'), | |
| 74 | + 'file.mimetypes' => $this->getMimeTypeErrorMessage($mediaKind, $hasAudioUpload), | |
| 69 | 75 | /* translators: %1$s is the maximum file size value, %2$s is the size unit (KB, MB, etc.) */ |
| 70 | 76 | 'file.max' => sprintf(__('The file size must be less than %1$s%2$s.', 'fluent-community'), $maxFileSize, $maxFileUnit) |
| 71 | 77 | ]); |
| 72 | 78 | // File size check |
| @@ -85,10 +91,11 @@ | ||
| 85 | 91 | 'driver' => 'local', |
| 86 | 92 | 'media_path' => $file['path'], |
| 87 | 93 | 'media_url' => $file['url'], |
| 88 | 94 | 'settings' => [ |
| 89 | - 'src' => $file['url'], | |
| 90 | - 'title' => $file['original_name'] | |
| 95 | + 'src' => $file['url'], | |
| 96 | + 'title' => $file['original_name'], | |
| 97 | + 'viewType' => $this->resolveViewType($file, $mediaKind) | |
| 91 | 98 | ] |
| 92 | 99 | ]; |
| 93 | 100 | |
| 94 | 101 | $mediaData = apply_filters('fluent_community/media_upload_data', $mediaData, $file); |
| @@ -125,8 +132,102 @@ | ||
| 125 | 132 | ] |
| 126 | 133 | ]; |
| 127 | 134 | } |
| 128 | 135 | |
| 136 | + /** | |
| 137 | + * Whether the current user may edit an audio media's metadata. | |
| 138 | + * Owner (upload sets user_id) or a moderator/site admin. | |
| 139 | + */ | |
| 140 | + public function canEditAudioMedia($media) | |
| 141 | + { | |
| 142 | + if (!$media) { | |
| 143 | + return false; | |
| 144 | + } | |
| 145 | + // Read user_id via the model accessor. A (array) cast of a WPFluent model exposes | |
| 146 | + // the protected $attributes under a mangled key, not a flat user_id. | |
| 147 | + if ((int) $media->user_id === (int) get_current_user_id()) { | |
| 148 | + return true; | |
| 149 | + } | |
| 150 | + return Helper::isModerator() || Helper::isSiteAdmin(); | |
| 151 | + } | |
| 152 | + | |
| 153 | + /** | |
| 154 | + * Update an audio media's display title and poster thumbnail from the composer. | |
| 155 | + */ | |
| 156 | + public function updateAudioMeta(Request $request) | |
| 157 | + { | |
| 158 | + $media = Media::find(intval($request->get('media_id'))); | |
| 159 | + if (!$media || !$this->canEditAudioMedia($media)) { | |
| 160 | + return $this->sendError([ | |
| 161 | + 'message' => __('You are not allowed to edit this audio', 'fluent-community'), | |
| 162 | + ], 403); | |
| 163 | + } | |
| 164 | + | |
| 165 | + // Only fluent-player audio media may be edited through this endpoint. | |
| 166 | + if ($media->media_type !== 'fluent_player' || !$this->isAudioMedia((array) $media->settings)) { | |
| 167 | + return $this->sendError([ | |
| 168 | + 'message' => __('This media is not an editable audio', 'fluent-community'), | |
| 169 | + ], 422); | |
| 170 | + } | |
| 171 | + | |
| 172 | + $settings = (array) $media->settings; | |
| 173 | + $title = $request->getSafe('title', 'sanitize_text_field', ''); | |
| 174 | + $posterSrc = $request->getSafe('posterSrc', 'sanitize_url', ''); | |
| 175 | + | |
| 176 | + if ($title !== '') { | |
| 177 | + $settings['title'] = $title; | |
| 178 | + } | |
| 179 | + if ($posterSrc !== '') { | |
| 180 | + $settings['posterSrc'] = $posterSrc; | |
| 181 | + // poster_is_custom gates whether the render path shows the custom thumbnail | |
| 182 | + // (see show_poster in generateFluentPlayerHtml). | |
| 183 | + $settings['poster_is_custom'] = true; | |
| 184 | + } else { | |
| 185 | + $settings['posterSrc'] = ''; | |
| 186 | + $settings['poster_is_custom'] = false; | |
| 187 | + } | |
| 188 | + | |
| 189 | + $media->settings = $settings; | |
| 190 | + $media->save(); | |
| 191 | + | |
| 192 | + return [ | |
| 193 | + 'media' => [ | |
| 194 | + 'media_id' => $media->id, | |
| 195 | + 'settings' => $media->settings, | |
| 196 | + ], | |
| 197 | + ]; | |
| 198 | + } | |
| 199 | + | |
| 200 | + private function resolveViewType($file, $mediaKind) | |
| 201 | + { | |
| 202 | + $audioExtensions = ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'oga', 'flac']; | |
| 203 | + $name = Arr::get($file, 'original_name', Arr::get($file, 'url', '')); | |
| 204 | + $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); | |
| 205 | + | |
| 206 | + if (in_array($extension, $audioExtensions, true)) { | |
| 207 | + return 'audio'; | |
| 208 | + } | |
| 209 | + | |
| 210 | + if ($extension) { | |
| 211 | + return 'video'; | |
| 212 | + } | |
| 213 | + | |
| 214 | + return $mediaKind === 'audio' ? 'audio' : 'video'; | |
| 215 | + } | |
| 216 | + | |
| 217 | + private function getMimeTypeErrorMessage($mediaKind, $hasAudioUpload) | |
| 218 | + { | |
| 219 | + if ($mediaKind === 'audio') { | |
| 220 | + return __('The file must be a valid audio (MP3, WAV, M4A, AAC, OGG, FLAC) type.', 'fluent-community'); | |
| 221 | + } | |
| 222 | + | |
| 223 | + if ($mediaKind === 'video' || !$hasAudioUpload) { | |
| 224 | + return __('The file must be a valid video (MP4, WebM, MOV) type.', 'fluent-community'); | |
| 225 | + } | |
| 226 | + | |
| 227 | + return __('The file must be a valid video (MP4, WebM, MOV) or audio (MP3, WAV, M4A, AAC, OGG, FLAC) type.', 'fluent-community'); | |
| 228 | + } | |
| 229 | + | |
| 129 | 230 | public function getFluentPlayerContent(Request $request) |
| 130 | 231 | { |
| 131 | 232 | if (!defined('FLUENT_PLAYER_VERSION')) { |
| 132 | 233 | return [ |
| @@ -134,14 +235,34 @@ | ||
| 134 | 235 | ]; |
| 135 | 236 | } |
| 136 | 237 | $mediaId = intval($request->get('media_id')); |
| 137 | 238 | $instanceKey = sanitize_key($request->get('player_instance_key')); |
| 138 | - $shareUrl = esc_url_raw($request->get('share_url')); | |
| 239 | + $shareUrl = esc_url_raw((string) $request->get('share_url', '')); | |
| 139 | 240 | $media = Media::find($mediaId); |
| 140 | 241 | if (!$media) { |
| 141 | - $media = (object) $request->all(); | |
| 142 | - } else if ($shareUrl) { | |
| 143 | - $media->share_url = $shareUrl; | |
| 242 | + // Non-DB media (external embed / share URL) from a synthetic hash id: build from | |
| 243 | + // allowlisted scalars only. Never trust request `settings`/`layers` — accepting | |
| 244 | + // them lets an anon caller inject shortcode layers into FluentPlayer's renderer. | |
| 245 | + $media = (object) [ | |
| 246 | + 'url' => esc_url_raw((string) $request->get('url', '')), | |
| 247 | + 'title' => sanitize_text_field($request->get('title')), | |
| 248 | + 'image' => esc_url_raw((string) $request->get('image', '')), | |
| 249 | + 'share_url' => esc_url_raw((string) $request->get('share_url', '')), | |
| 250 | + 'provider' => sanitize_text_field($request->get('provider')), | |
| 251 | + 'type' => sanitize_text_field($request->get('type')), | |
| 252 | + ]; | |
| 253 | + } else { | |
| 254 | + $currentUserId = get_current_user_id(); | |
| 255 | + $canView = ($currentUserId && (int) $media->user_id === $currentUserId) | |
| 256 | + || ($media->feed_id && Feed::byUserAccess($currentUserId) | |
| 257 | + ->byContentModerationAccessStatus($this->getUser()) | |
| 258 | + ->find($media->feed_id)); | |
| 259 | + if (!$canView) { | |
| 260 | + return ['html' => '']; | |
| 261 | + } | |
| 262 | + if ($shareUrl) { | |
| 263 | + $media->share_url = $shareUrl; | |
| 264 | + } | |
| 144 | 265 | } |
| 145 | 266 | return $this->generateFluentPlayerHtml($mediaId, $media, $instanceKey); |
| 146 | 267 | } |
| 147 | 268 | |
| @@ -180,8 +301,23 @@ | ||
| 180 | 301 | $mediaSettings['is_short'] = true; |
| 181 | 302 | } |
| 182 | 303 | |
| 183 | 304 | $mediaSettings = wp_parse_args($mediaSettings, $this->getFluentplayerDefaultsSettings()); |
| 305 | + | |
| 306 | + if ($this->isAudioMedia($mediaSettings)) { | |
| 307 | + // Only show a thumbnail block when the author uploaded one; otherwise render a | |
| 308 | + // clean player with no poster (per design). poster_is_custom is set by the | |
| 309 | + // audio edit modal / updateAudioMeta. | |
| 310 | + $hasCustomPoster = Arr::get($mediaSettings, 'posterSrc', '') !== '' | |
| 311 | + && filter_var(Arr::get($mediaSettings, 'poster_is_custom'), FILTER_VALIDATE_BOOLEAN); | |
| 312 | + $mediaSettings['show_poster'] = $hasCustomPoster; | |
| 313 | + // Show the inline "1x" speed control instead of the settings gear menu. | |
| 314 | + if (!isset($mediaSettings['controls']) || !is_array($mediaSettings['controls'])) { | |
| 315 | + $mediaSettings['controls'] = []; | |
| 316 | + } | |
| 317 | + $mediaSettings['controls']['settings'] = false; | |
| 318 | + } | |
| 319 | + | |
| 184 | 320 | $formattedMedia['settings'] = $mediaSettings; |
| 185 | 321 | $formattedMedia['deep_link_ref'] = $playerKey; |
| 186 | 322 | |
| 187 | 323 | // Generate the HTML using fluent-player's view system |
| @@ -207,40 +343,42 @@ | ||
| 207 | 343 | 'media' => $formattedMedia |
| 208 | 344 | ]; |
| 209 | 345 | } |
| 210 | 346 | |
| 347 | + private function isAudioMedia($settings) | |
| 348 | + { | |
| 349 | + if (class_exists('\FluentPlayer\App\Helpers\Helper') | |
| 350 | + && method_exists('\FluentPlayer\App\Helpers\Helper', 'isAudioSource')) { | |
| 351 | + return \FluentPlayer\App\Helpers\Helper::isAudioSource($settings); | |
| 352 | + } | |
| 353 | + return Arr::get($settings, 'viewType') === 'audio'; | |
| 354 | + } | |
| 355 | + | |
| 211 | 356 | private function generateFluentPlayerCustomStyle($instanceId, $settings) |
| 212 | 357 | { |
| 213 | 358 | $customCss = ''; |
| 214 | - $playerWidth = Arr::get($settings, 'playerWidth'); | |
| 215 | - if ($playerWidth) { | |
| 359 | + $isAudio = $this->isAudioMedia($settings); | |
| 360 | + $playerWidth = intval(Arr::get($settings, 'playerWidth')); | |
| 361 | + if ($playerWidth > 0) { | |
| 216 | 362 | $customCss .= " |
| 217 | 363 | #fluent_player_" . esc_attr($instanceId) . " .fluent-player-container { |
| 218 | - width: " . esc_attr($playerWidth) . 'px' . "; | |
| 364 | + width: " . $playerWidth . 'px' . "; | |
| 219 | 365 | } |
| 220 | 366 | "; |
| 221 | 367 | } |
| 222 | - $brandingColor = Arr::get($settings, 'brandColor', ''); | |
| 223 | - if ($brandingColor) { | |
| 368 | + $colorVars = array_filter([ | |
| 369 | + '--media-brand' => Bootstrap::sanitizeColor(Arr::get($settings, 'brandColor', '')), | |
| 370 | + '--fp-control-bar-bg' => Bootstrap::sanitizeColor(Arr::get($settings, 'controlBarColor', '')), | |
| 371 | + '--media-play-color' => Bootstrap::sanitizeColor(Arr::get($settings, 'playButtonColor', '')), | |
| 372 | + '--media-play-bg' => Bootstrap::sanitizeColor(Arr::get($settings, 'playButtonBgColor', '')) | |
| 373 | + ]); | |
| 374 | + if ($colorVars) { | |
| 224 | 375 | $customCss .= " |
| 225 | - #fluent_player_" . esc_attr($instanceId) . " { | |
| 226 | - --media-brand: " . esc_attr($brandingColor) . "; | |
| 227 | - } | |
| 228 | - "; | |
| 229 | - } | |
| 230 | - $brandingColor = Arr::get($settings, 'brandColor', ''); | |
| 231 | - $controlBarColor = Arr::get($settings, 'controlBarColor', ''); | |
| 232 | - if ($brandingColor || $controlBarColor) { | |
| 233 | - $customCss .= " | |
| 234 | 376 | #fluent_player_" . esc_attr($instanceId) . " {"; |
| 235 | - if ($brandingColor) { | |
| 377 | + foreach ($colorVars as $varName => $varValue) { | |
| 236 | 378 | $customCss .= " |
| 237 | - --media-brand: " . esc_attr($brandingColor) . ";"; | |
| 379 | + " . $varName . ": " . esc_attr($varValue) . ";"; | |
| 238 | 380 | } |
| 239 | - if ($controlBarColor) { | |
| 240 | - $customCss .= " | |
| 241 | - --fp-control-bar-bg: " . esc_attr($controlBarColor) . ";"; | |
| 242 | - } | |
| 243 | 381 | $customCss .= " |
| 244 | 382 | } |
| 245 | 383 | "; |
| 246 | 384 | } |
| @@ -254,10 +392,174 @@ | ||
| 254 | 392 | } |
| 255 | 393 | "; |
| 256 | 394 | } |
| 257 | 395 | |
| 396 | + if ($isAudio) { | |
| 397 | + // Audio follows the FluentCommunity color schema (Utility::getColorSchemas()). | |
| 398 | + // The player renders inside the portal DOM and inherits the --fcom-* CSS vars that | |
| 399 | + // generateCss() emits on body (and html.dark body), so theme/skin/dark changes recolor | |
| 400 | + // audio live with no re-save. The stock "modern" audio skin is built for a dark | |
| 401 | + // background (white title, translucent-white controls, brand-colored bar); we override | |
| 402 | + // those to a flat, theme-surface card. Play circle + progress fill use the auto- | |
| 403 | + // inverting primary_text/primary_bg pair so the same rules match a light card | |
| 404 | + // (dark on light) and a dark card (light on dark) without per-theme branching. | |
| 405 | + $playerId = '#fluent_player_' . esc_attr($instanceId); | |
| 406 | + $customCss .= " | |
| 407 | + {$playerId} { | |
| 408 | + --media-brand: var(--fcom-primary-text, #19283a); | |
| 409 | + --fp-control-bar-bg: transparent; | |
| 410 | + } | |
| 411 | + {$playerId} .fp-audio-layout { | |
| 412 | + background-color: var(--fcom-secondary-content-bg, var(--fcom-active-bg, #f0f2f5)); | |
| 413 | + border-radius: 12px; | |
| 414 | + } | |
| 415 | + {$playerId} .fp-audio-main-controls { | |
| 416 | + background: transparent; | |
| 417 | + } | |
| 418 | + {$playerId} .fp-audio-title { | |
| 419 | + color: var(--fcom-primary-text, #19283a); | |
| 420 | + font-size: 14px; | |
| 421 | + font-weight: 600; | |
| 422 | + } | |
| 423 | + {$playerId} .fp-audio-subtitle { | |
| 424 | + color: var(--fcom-primary-text, #19283a); | |
| 425 | + } | |
| 426 | + {$playerId} media-play-button { | |
| 427 | + background: var(--fcom-primary-text, #19283a); | |
| 428 | + } | |
| 429 | + {$playerId} media-play-button media-icon { | |
| 430 | + color: var(--fcom-primary-bg, #ffffff); | |
| 431 | + } | |
| 432 | + {$playerId} media-seek-button { | |
| 433 | + background: transparent; | |
| 434 | + } | |
| 435 | + {$playerId} media-time-slider .fp-media-slider-track { | |
| 436 | + background: var(--fcom-secondary-border, #9CA3AF); | |
| 437 | + background: color-mix(in srgb, var(--fcom-primary-text, #19283a) 20%, transparent); | |
| 438 | + } | |
| 439 | + {$playerId} media-time-slider .fp-media-slider-track-fill { | |
| 440 | + background: var(--fcom-primary-text, #19283a); | |
| 441 | + } | |
| 442 | + {$playerId} .fp-seek-backward-10, | |
| 443 | + {$playerId} .fp-seek-forward-10, | |
| 444 | + {$playerId} .fp-media-mute-icon, | |
| 445 | + {$playerId} .fp-media-volume-low-icon, | |
| 446 | + {$playerId} .fp-media-volume-high-icon, | |
| 447 | + {$playerId} media-seek-button media-icon, | |
| 448 | + {$playerId} media-icon[type=\"odometer\"], | |
| 449 | + {$playerId} media-icon[type=\"settings\"] { | |
| 450 | + color: var(--fcom-secondary-text, #525866); | |
| 451 | + } | |
| 452 | + {$playerId} .fp-media-time-group, | |
| 453 | + {$playerId} .fp-media-time-group media-time, | |
| 454 | + {$playerId} .fp-media-time-divider { | |
| 455 | + color: var(--fcom-primary-text, #19283a); | |
| 456 | + } | |
| 457 | + "; | |
| 458 | + | |
| 459 | + // Exact layout: large play circle on the left (spanning title + controls), | |
| 460 | + // a single control row (seek / seek / volume / speed / time) and a full-width | |
| 461 | + // progress bar at the bottom. The volume slider is hidden until hover. | |
| 462 | + $customCss .= " | |
| 463 | + {$playerId} .fp-audio-layout { | |
| 464 | + position: relative; | |
| 465 | + padding: 22px 24px 14px; | |
| 466 | + } | |
| 467 | + {$playerId} .fp-audio-layout .fp-audio-content { | |
| 468 | + padding: 0 0 0 82px; | |
| 469 | + gap: 6px; | |
| 470 | + width: 100%; | |
| 471 | + } | |
| 472 | + {$playerId} .fp-audio-header { | |
| 473 | + padding-left: 8px; | |
| 474 | + } | |
| 475 | + {$playerId} media-play-button { | |
| 476 | + position: absolute; | |
| 477 | + left: 22px; | |
| 478 | + top: 50%; | |
| 479 | + transform: translateY(-50%); | |
| 480 | + width: 64px; | |
| 481 | + height: 64px; | |
| 482 | + min-width: 64px; | |
| 483 | + } | |
| 484 | + {$playerId} .fp-audio-main-controls { | |
| 485 | + display: grid; | |
| 486 | + grid-template-columns: max-content max-content max-content max-content 1fr max-content; | |
| 487 | + grid-template-areas: \"sb sf vol spd play time\" \"pr pr pr pr pr pr\"; | |
| 488 | + align-items: center; | |
| 489 | + column-gap: 0; | |
| 490 | + row-gap: 6px; | |
| 491 | + padding: 0; | |
| 492 | + width: 100%; | |
| 493 | + } | |
| 494 | + {$playerId} .fp-audio-main-controls media-tooltip:nth-of-type(1) { grid-area: sb; margin: 0; } | |
| 495 | + {$playerId} .fp-audio-main-controls media-tooltip:nth-of-type(2) { grid-area: play; } | |
| 496 | + {$playerId} .fp-audio-main-controls media-tooltip:nth-of-type(3) { grid-area: sf; margin: 0; } | |
| 497 | + {$playerId} media-seek-button, | |
| 498 | + {$playerId} .fp-audio-volume-group media-mute-button { | |
| 499 | + width: 32px; | |
| 500 | + height: 32px; | |
| 501 | + min-width: 32px; | |
| 502 | + } | |
| 503 | + {$playerId} .fp-audio-secondary-controls media-menu-button { min-width: 32px; padding: 0 6px; } | |
| 504 | + {$playerId} .fp-audio-volume-group { grid-area: vol; margin: 0; overflow: visible; } | |
| 505 | + {$playerId} .fp-audio-secondary-controls { grid-area: spd; margin: 0; } | |
| 506 | + {$playerId} .fp-media-time-group { grid-area: time; margin: 0; justify-self: end; } | |
| 507 | + {$playerId} media-player[data-view-type=\"audio\"] media-time-slider, | |
| 508 | + {$playerId} media-time-slider { grid-area: pr; margin: 8px; } | |
| 509 | + {$playerId} media-seek-button:hover, | |
| 510 | + {$playerId} .fp-audio-volume-group media-mute-button:hover, | |
| 511 | + {$playerId} .fp-audio-secondary-controls media-menu-button:hover { | |
| 512 | + background: transparent; | |
| 513 | + } | |
| 514 | + {$playerId} .fp-audio-volume-group media-volume-slider { | |
| 515 | + width: 0; | |
| 516 | + max-width: 0; | |
| 517 | + min-width: 0; | |
| 518 | + opacity: 0; | |
| 519 | + overflow: hidden; | |
| 520 | + margin: 0; | |
| 521 | + transition: width .2s ease, opacity .2s ease, margin .2s ease; | |
| 522 | + } | |
| 523 | + {$playerId} .fp-audio-volume-group:hover media-volume-slider, | |
| 524 | + {$playerId} .fp-audio-volume-group:focus-within media-volume-slider { | |
| 525 | + width: 80px; | |
| 526 | + max-width: 80px; | |
| 527 | + opacity: 1; | |
| 528 | + margin-left: 6px; | |
| 529 | + } | |
| 530 | + {$playerId} .fp-speed-menu .fcom_speed_label { | |
| 531 | + font-weight: 600; | |
| 532 | + font-size: 15px; | |
| 533 | + line-height: 1; | |
| 534 | + color: var(--fcom-primary-text, #19283a); | |
| 535 | + } | |
| 536 | + "; | |
| 537 | + | |
| 538 | + // When a custom thumbnail is set, lay the card out as poster -> play -> content | |
| 539 | + // (matching the reference). The poster sits on the left, the play circle after it. | |
| 540 | + $customCss .= " | |
| 541 | + {$playerId} .fp-audio-layout:not(.fp-audio-no-poster) { | |
| 542 | + gap: 16px; | |
| 543 | + padding-left: 16px; | |
| 544 | + } | |
| 545 | + {$playerId} .fp-audio-layout:not(.fp-audio-no-poster) .fp-audio-poster { | |
| 546 | + width: 96px; | |
| 547 | + height: 96px; | |
| 548 | + min-width: 96px; | |
| 549 | + border-radius: 12px; | |
| 550 | + } | |
| 551 | + {$playerId} .fp-audio-layout:not(.fp-audio-no-poster) media-play-button { | |
| 552 | + left: 128px; | |
| 553 | + } | |
| 554 | + {$playerId} .fp-audio-layout:not(.fp-audio-no-poster) .fp-audio-content { | |
| 555 | + padding-left: 82px; | |
| 556 | + } | |
| 557 | + "; | |
| 558 | + } | |
| 559 | + | |
| 258 | 560 | $aspectRatio = Arr::get($settings, 'aspectRatio'); |
| 259 | - if ($aspectRatio && $aspectRatio != 'original') { | |
| 561 | + if ($aspectRatio && $aspectRatio != 'original' && preg_match('/^\d+:\d+$/', $aspectRatio)) { | |
| 260 | 562 | $cssAspectRatio = preg_replace('/^(\d+):(\d+)$/', '$1/$2', $aspectRatio); |
| 261 | 563 | $customCss .= " |
| 262 | 564 | #fluent_player_" . esc_attr($instanceId) . " { |
| 263 | 565 | aspect-ratio: " . esc_attr($cssAspectRatio) . "; |
| @@ -265,9 +567,9 @@ | ||
| 265 | 567 | #fluent_player_" . esc_attr($instanceId) . " media-player[data-view-type='video'] { |
| 266 | 568 | aspect-ratio: " . esc_attr($cssAspectRatio) . "; |
| 267 | 569 | } |
| 268 | 570 | "; |
| 269 | - } else { | |
| 571 | + } else if (!$isAudio) { | |
| 270 | 572 | $customCss .= " |
| 271 | 573 | #fluent_player_" . esc_attr($instanceId) . " { |
| 272 | 574 | max-width: 100%; |
| 273 | 575 | min-height: 300px; |
| @@ -295,8 +597,9 @@ | ||
| 295 | 597 | 'viewType' => 'video', |
| 296 | 598 | 'brandColor' => '#4a90e2', |
| 297 | 599 | 'aspectRatio' => 'original', |
| 298 | 600 | 'playerWidth' => '', |
| 601 | + 'playsInline' => true | |
| 299 | 602 | ]; |
| 300 | 603 | $mediaSettings = Bootstrap::getSettings(); |
| 301 | 604 | if (Arr::isTrue($mediaSettings, 'behaviors.muted_autoplay')) { |
| 302 | 605 | $mediaSettings['autoplay'] = true; |