| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentCommunity\Modules\Integrations\FluentPlayer\Http\Controllers; |
| 4 |
|
| 5 |
use FluentCommunity\App\Http\Controllers\Controller; |
| 6 |
use FluentCommunity\App\Models\Media; |
| 7 |
use FluentCommunity\App\Services\Helper; |
| 8 |
use FluentCommunity\App\Services\Libs\FileSystem; |
| 9 |
use FluentCommunity\Framework\Http\Request\Request; |
| 10 |
use FluentCommunity\Framework\Support\Arr; |
| 11 |
use FluentCommunity\Modules\Integrations\FluentPlayer\Bootstrap; |
| 12 |
|
| 13 |
class MediaController extends Controller |
| 14 |
{ |
| 15 |
public function uploadVideo(Request $request) |
| 16 |
{ |
| 17 |
$getFluentPlayerStatus = Bootstrap::getPluginStatus(); |
| 18 |
if ($getFluentPlayerStatus !== 'active') { |
| 19 |
return $this->sendError([ |
| 20 |
'message' => __('Uploading videos is not allowed. Activate FluentPlayer to upload videos.', 'fluent-community') |
| 21 |
]); |
| 22 |
} |
| 23 |
|
| 24 |
$fluentPlayerSettings = Bootstrap::getSettings(); |
| 25 |
if (!Arr::isTrue($fluentPlayerSettings, 'video_upload')) { |
| 26 |
return $this->sendError([ |
| 27 |
'message' => __('Uploading videos is not allowed', 'fluent-community') |
| 28 |
]); |
| 29 |
} |
| 30 |
$uploadRole = Arr::get($fluentPlayerSettings, 'video_upload_role'); |
| 31 |
$uploadRole = $uploadRole ?: 'admin'; |
| 32 |
$isAllowed = false; |
| 33 |
if ('admin' === $uploadRole && Helper::isSiteAdmin()) { |
| 34 |
$isAllowed = true; |
| 35 |
} else if ('admin_moderator' === $uploadRole && (Helper::isModerator() || Helper::isSiteAdmin())) { |
| 36 |
$isAllowed = true; |
| 37 |
} else if ('everyone' === $uploadRole) { |
| 38 |
$isAllowed = true; |
| 39 |
} |
| 40 |
if (!$isAllowed) { |
| 41 |
return $this->sendError([ |
| 42 |
'message' => __('You do not have permission to upload videos', 'fluent-community') |
| 43 |
]); |
| 44 |
} |
| 45 |
|
| 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 |
]); |
| 54 |
$maxFileUnit = apply_filters('fluent_community/video_upload_max_file_unit', 'MB'); |
| 55 |
$maxFileSize = apply_filters('fluent_community/video_upload_max_file_size', 300); |
| 56 |
$allowedFileSize = $maxFileSize; |
| 57 |
if (strtoupper($maxFileUnit) == 'MB') { |
| 58 |
$allowedFileSize = $maxFileSize * 1024; |
| 59 |
} else if (strtoupper($maxFileUnit) == 'GB') { |
| 60 |
$allowedFileSize = $maxFileSize * 1024 * 1024; |
| 61 |
} |
| 62 |
|
| 63 |
$allowedTypesString = implode(',', $allowedVideoTypes); |
| 64 |
|
| 65 |
$files = $this->validate($request->files(), [ |
| 66 |
'file' => 'mimetypes:' . $allowedTypesString . '|max:' . $allowedFileSize, |
| 67 |
], [ |
| 68 |
'file.mimetypes' => __('The file must be a valid video type (MP4, M3U8, MPD, WebM, MOV).', 'fluent-community'), |
| 69 |
/* translators: %1$s is the maximum file size value, %2$s is the size unit (KB, MB, etc.) */ |
| 70 |
'file.max' => sprintf(__('The file size must be less than %1$s%2$s.', 'fluent-community'), $maxFileSize, $maxFileUnit) |
| 71 |
]); |
| 72 |
// File size check |
| 73 |
if ($error = Helper::checkUploadSizeError()) { |
| 74 |
return $this->sendError($error, 413); |
| 75 |
} |
| 76 |
|
| 77 |
$uploadedFiles = FileSystem::put($files); |
| 78 |
$file = $uploadedFiles[0]; |
| 79 |
|
| 80 |
$upload_dir = wp_upload_dir(); |
| 81 |
$file['path'] = $upload_dir['basedir'] . '/fluent-community/' . $file['file']; |
| 82 |
|
| 83 |
$mediaData = [ |
| 84 |
'media_type' => 'fluent_player', |
| 85 |
'driver' => 'local', |
| 86 |
'media_path' => $file['path'], |
| 87 |
'media_url' => $file['url'], |
| 88 |
'settings' => [ |
| 89 |
'src' => $file['url'], |
| 90 |
'title' => $file['original_name'] |
| 91 |
] |
| 92 |
]; |
| 93 |
|
| 94 |
$mediaData = apply_filters('fluent_community/media_upload_data', $mediaData, $file); |
| 95 |
|
| 96 |
if (is_wp_error($mediaData)) { |
| 97 |
return $this->sendError([ |
| 98 |
'message' => $mediaData->get_error_message(), |
| 99 |
'errors' => $mediaData->get_error_data() |
| 100 |
]); |
| 101 |
} |
| 102 |
|
| 103 |
if (!$mediaData) { |
| 104 |
return $this->sendError([ |
| 105 |
'message' => __('Error while uploading the video', 'fluent-community') |
| 106 |
]); |
| 107 |
} |
| 108 |
$mediaData['settings']['src'] = Arr::get($mediaData, 'media_url', $file['url']); |
| 109 |
|
| 110 |
// Disable crossorigin for cloud storage drivers that may lack CORS headers |
| 111 |
$driver = Arr::get($mediaData, 'driver', 'local'); |
| 112 |
if ($driver === 's3') { |
| 113 |
$mediaData['settings']['crossorigin'] = false; |
| 114 |
} |
| 115 |
|
| 116 |
$media = Media::create($mediaData); |
| 117 |
return [ |
| 118 |
'media' => [ |
| 119 |
'media_id' => $media->id, |
| 120 |
'url' => $media->public_url, |
| 121 |
'media_key' => $media->media_key, |
| 122 |
'type' => $media->media_type, |
| 123 |
'settings' => $media->settings, |
| 124 |
'html' => '' |
| 125 |
] |
| 126 |
]; |
| 127 |
} |
| 128 |
|
| 129 |
public function getFluentPlayerContent(Request $request) |
| 130 |
{ |
| 131 |
if (!defined('FLUENT_PLAYER_VERSION')) { |
| 132 |
return [ |
| 133 |
'html' => '', |
| 134 |
]; |
| 135 |
} |
| 136 |
$mediaId = intval($request->get('media_id')); |
| 137 |
$instanceKey = sanitize_key($request->get('player_instance_key')); |
| 138 |
$shareUrl = esc_url_raw($request->get('share_url')); |
| 139 |
$media = Media::find($mediaId); |
| 140 |
if (!$media) { |
| 141 |
$media = (object) $request->all(); |
| 142 |
} else if ($shareUrl) { |
| 143 |
$media->share_url = $shareUrl; |
| 144 |
} |
| 145 |
return $this->generateFluentPlayerHtml($mediaId, $media, $instanceKey); |
| 146 |
} |
| 147 |
|
| 148 |
private function generateFluentPlayerHtml($mediaId, $media, $instanceKey = '') |
| 149 |
{ |
| 150 |
$mediaVarName = 'fluentPlayerMedia_' . $mediaId . '_' . wp_rand(1000, 9999); |
| 151 |
$playerKey = 'fcom_' . $mediaId; |
| 152 |
$instanceId = $playerKey . ($instanceKey ? '_' . $instanceKey : ''); |
| 153 |
$mediaSettings = []; |
| 154 |
$formattedMedia = [ |
| 155 |
'ID' => $mediaId |
| 156 |
]; |
| 157 |
if (!empty($media->settings)) { |
| 158 |
$mediaSettings = $media->settings; |
| 159 |
} |
| 160 |
if (empty($mediaSettings['src']) && !empty($media->url)) { |
| 161 |
$mediaSettings['src'] = $media->url; |
| 162 |
} |
| 163 |
if (empty($mediaSettings['title']) && !empty($media->title)) { |
| 164 |
$mediaSettings['title'] = $media->title; |
| 165 |
} |
| 166 |
if (empty($mediaSettings['posterSrc']) && !empty($media->image)) { |
| 167 |
$mediaSettings['posterSrc'] = $media->image; |
| 168 |
} |
| 169 |
if (empty($mediaSettings['share_url']) && !empty($media->share_url)) { |
| 170 |
$mediaSettings['share_url'] = esc_url_raw($media->share_url); |
| 171 |
} |
| 172 |
$src = Arr::get($mediaSettings, 'src', ''); |
| 173 |
// Normalize YouTube /live/ URLs to /watch?v= so Vidstack can resolve them |
| 174 |
if ($src && strpos($src, 'youtube.com/live/') !== false) { |
| 175 |
$src = preg_replace('/youtube\.com\/live\/([^?&#]+)\??/', 'youtube.com/watch?v=$1&', $src); |
| 176 |
$src = rtrim($src, '&'); |
| 177 |
$mediaSettings['src'] = $src; |
| 178 |
} |
| 179 |
if ($src && preg_match('#youtube\.com/shorts/#i', $src)) { |
| 180 |
$mediaSettings['is_short'] = true; |
| 181 |
} |
| 182 |
|
| 183 |
$mediaSettings = wp_parse_args($mediaSettings, $this->getFluentplayerDefaultsSettings()); |
| 184 |
$formattedMedia['settings'] = $mediaSettings; |
| 185 |
$formattedMedia['deep_link_ref'] = $playerKey; |
| 186 |
|
| 187 |
// Generate the HTML using fluent-player's view system |
| 188 |
ob_start(); |
| 189 |
if (class_exists('\FluentPlayer\App\App')) { |
| 190 |
try { |
| 191 |
$fluentPlayerApp = \FluentPlayer\App\App::getInstance(); |
| 192 |
if ($fluentPlayerApp) { |
| 193 |
$fluentPlayerApp->view->render('player', [ |
| 194 |
'media_id' => $mediaId, |
| 195 |
'instance_id' => $instanceId, |
| 196 |
'media_var_name' => $mediaVarName, |
| 197 |
'player_ref' => $playerKey, |
| 198 |
'settings' => $mediaSettings |
| 199 |
]); |
| 200 |
} |
| 201 |
} catch (\Exception $e) { |
| 202 |
} |
| 203 |
} |
| 204 |
$html = ob_get_clean(); |
| 205 |
return [ |
| 206 |
'html' => $html ? $html . $this->generateFluentPlayerCustomStyle($instanceId, $mediaSettings) : '', |
| 207 |
'media' => $formattedMedia |
| 208 |
]; |
| 209 |
} |
| 210 |
|
| 211 |
private function generateFluentPlayerCustomStyle($instanceId, $settings) |
| 212 |
{ |
| 213 |
$customCss = ''; |
| 214 |
$playerWidth = Arr::get($settings, 'playerWidth'); |
| 215 |
if ($playerWidth) { |
| 216 |
$customCss .= " |
| 217 |
#fluent_player_" . esc_attr($instanceId) . " .fluent-player-container { |
| 218 |
width: " . esc_attr($playerWidth) . 'px' . "; |
| 219 |
} |
| 220 |
"; |
| 221 |
} |
| 222 |
$brandingColor = Arr::get($settings, 'brandColor', ''); |
| 223 |
if ($brandingColor) { |
| 224 |
$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 |
#fluent_player_" . esc_attr($instanceId) . " {"; |
| 235 |
if ($brandingColor) { |
| 236 |
$customCss .= " |
| 237 |
--media-brand: " . esc_attr($brandingColor) . ";"; |
| 238 |
} |
| 239 |
if ($controlBarColor) { |
| 240 |
$customCss .= " |
| 241 |
--fp-control-bar-bg: " . esc_attr($controlBarColor) . ";"; |
| 242 |
} |
| 243 |
$customCss .= " |
| 244 |
} |
| 245 |
"; |
| 246 |
} |
| 247 |
if (!empty(Arr::get($settings, 'posterSrc', ''))) { |
| 248 |
$customCss .= " |
| 249 |
#fluent_player_" . esc_attr($instanceId) . " .fluent-player-container { |
| 250 |
background-image: url('" . esc_url(Arr::get($settings, 'posterSrc', '')) . "'); |
| 251 |
background-size: cover; |
| 252 |
background-position: center; |
| 253 |
min-height: 100%; |
| 254 |
} |
| 255 |
"; |
| 256 |
} |
| 257 |
|
| 258 |
$aspectRatio = Arr::get($settings, 'aspectRatio'); |
| 259 |
if ($aspectRatio && $aspectRatio != 'original') { |
| 260 |
$cssAspectRatio = preg_replace('/^(\d+):(\d+)$/', '$1/$2', $aspectRatio); |
| 261 |
$customCss .= " |
| 262 |
#fluent_player_" . esc_attr($instanceId) . " { |
| 263 |
aspect-ratio: " . esc_attr($cssAspectRatio) . "; |
| 264 |
} |
| 265 |
#fluent_player_" . esc_attr($instanceId) . " media-player[data-view-type='video'] { |
| 266 |
aspect-ratio: " . esc_attr($cssAspectRatio) . "; |
| 267 |
} |
| 268 |
"; |
| 269 |
} else { |
| 270 |
$customCss .= " |
| 271 |
#fluent_player_" . esc_attr($instanceId) . " { |
| 272 |
max-width: 100%; |
| 273 |
min-height: 300px; |
| 274 |
} |
| 275 |
|
| 276 |
@media (max-width: 768px) { |
| 277 |
#fluent_player_" . esc_attr($instanceId) . " { |
| 278 |
min-height: 200px; |
| 279 |
} |
| 280 |
} |
| 281 |
"; |
| 282 |
} |
| 283 |
if (!empty($customCss)) { |
| 284 |
return '<style>' . $customCss . '</style>'; |
| 285 |
} |
| 286 |
return ''; |
| 287 |
} |
| 288 |
|
| 289 |
private function getFluentplayerDefaultsSettings() |
| 290 |
{ |
| 291 |
$settings = [ |
| 292 |
'src' => '', |
| 293 |
'title' => '', |
| 294 |
'posterSrc' => '', |
| 295 |
'viewType' => 'video', |
| 296 |
'brandColor' => '#4a90e2', |
| 297 |
'aspectRatio' => 'original', |
| 298 |
'playerWidth' => '', |
| 299 |
]; |
| 300 |
$mediaSettings = Bootstrap::getSettings(); |
| 301 |
if (Arr::isTrue($mediaSettings, 'behaviors.muted_autoplay')) { |
| 302 |
$mediaSettings['autoplay'] = true; |
| 303 |
$mediaSettings['muted'] = true; |
| 304 |
} |
| 305 |
$settings['loadStrategy'] = 'idle'; // for SPA context this needs to be set to 'idle' |
| 306 |
$settings = array_merge($settings, $mediaSettings); |
| 307 |
|
| 308 |
// Apply iOS Safari compatibility (playsinline, preload; muted only when autoplay is on) |
| 309 |
if (class_exists('\FluentPlayer\App\Services\MediaService')) { |
| 310 |
[$isIosSafari, $iosSafariSettings] = \FluentPlayer\App\Services\MediaService::getIOSSafariSettings($settings); |
| 311 |
if ($isIosSafari) { |
| 312 |
$settings = array_merge($settings, $iosSafariSettings); |
| 313 |
} |
| 314 |
} |
| 315 |
|
| 316 |
return apply_filters('fluent_community/fluentplayer_defaults_settings', $settings); |
| 317 |
} |
| 318 |
} |
| 319 |
|