feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements

This commit is contained in:
Yiannis Christodoulou
2025-10-20 15:30:00 +03:00
committed by GitHub
parent b39072c8ae
commit a5e6e7b9ca
362 changed files with 62326 additions and 238721 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 B

View File

@@ -0,0 +1,231 @@
import React, { useEffect, useRef } from 'react';
/**
* VideoJSEmbed - A React component that embeds the MediaCMS video js
*
* This component dynamically loads the video js's CSS and JS files,
* then creates the required DOM element for the video js to mount to.
*
* Usage:
* <VideoJSEmbed
* data={}
* siteUrl="http://localhost"
* />
*/
const VideoJSEmbed = ({
data,
useRoundedCorners,
isPlayList,
playerVolume,
playerSoundMuted,
videoQuality,
videoPlaybackSpeed,
inTheaterMode,
siteId,
siteUrl,
info,
cornerLayers,
sources,
poster,
previewSprite,
subtitlesInfo,
enableAutoplay,
inEmbed,
hasTheaterMode,
hasNextLink,
nextLink,
hasPreviousLink,
errorMessage,
onClickNextCallback,
onClickPreviousCallback,
onStateUpdateCallback,
onPlayerInitCallback,
}) => {
const containerRef = useRef(null);
const assetsLoadedRef = useRef(false);
const playerInstanceRef = useRef(null);
const inEmbedRef = useRef(inEmbed);
// Helper function to get URL parameters
const getUrlParameter = (name) => {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
};
useEffect(() => {
// Update the ref whenever inEmbed changes
inEmbedRef.current = inEmbed;
// Set the global MEDIA_DATA that the video js expects
if (typeof window !== 'undefined') {
// Get URL parameters for autoplay, muted, and timestamp
const urlTimestamp = getUrlParameter('t');
const urlAutoplay = getUrlParameter('autoplay');
const urlMuted = getUrlParameter('muted');
window.MEDIA_DATA = {
data: data || {},
useRoundedCorners: useRoundedCorners,
isPlayList: isPlayList,
playerVolume: playerVolume || 0.5,
playerSoundMuted: playerSoundMuted || (urlMuted === '1'),
videoQuality: videoQuality || 'auto',
videoPlaybackSpeed: videoPlaybackSpeed || 1,
inTheaterMode: inTheaterMode || false,
siteId: siteId || '',
siteUrl: siteUrl || '',
info: info || {},
cornerLayers: cornerLayers || [],
sources: sources || [],
poster: poster || '',
previewSprite: previewSprite || null,
subtitlesInfo: subtitlesInfo || [],
enableAutoplay: enableAutoplay || (urlAutoplay === '1'),
inEmbed: inEmbed || false,
hasTheaterMode: hasTheaterMode || false,
hasNextLink: hasNextLink || false,
nextLink: nextLink || null,
hasPreviousLink: hasPreviousLink || false,
errorMessage: errorMessage || '',
// URL parameters
urlTimestamp: urlTimestamp ? parseInt(urlTimestamp, 10) : null,
urlAutoplay: urlAutoplay === '1',
urlMuted: urlMuted === '1',
onClickNextCallback: onClickNextCallback || null,
onClickPreviousCallback: onClickPreviousCallback || null,
onStateUpdateCallback: onStateUpdateCallback || null,
onPlayerInitCallback: (instance, elem) => {
// Store the player instance for timestamp functionality
playerInstanceRef.current = instance;
// Call the original callback if provided
if (onPlayerInitCallback) {
onPlayerInitCallback(instance, elem);
}
},
};
}
// Load assets only once
if (!assetsLoadedRef.current) {
loadVideoJSAssets();
assetsLoadedRef.current = true;
}
}, [data, siteUrl, inEmbed]);
// New effect to manually trigger VideoJS mounting for embed players
useEffect(() => {
if (inEmbed && containerRef.current) {
// Small delay to ensure DOM is fully ready, then trigger VideoJS mounting
const timer = setTimeout(() => {
// Try to trigger the VideoJS mount by dispatching a custom event
const event = new CustomEvent('triggerVideoJSMount', {
detail: { targetId: 'video-js-root-embed' }
});
document.dispatchEvent(event);
// Also try to trigger by calling the global function if it exists
if (typeof window !== 'undefined' && window.triggerVideoJSMount) {
window.triggerVideoJSMount();
}
}, 100);
return () => clearTimeout(timer);
}
}, [inEmbed, containerRef.current]);
// Set up timestamp click functionality
useEffect(() => {
const handleTimestampClick = (e) => {
if (e.target.classList.contains('video-timestamp')) {
e.preventDefault();
const timestamp = parseInt(e.target.dataset.timestamp, 10);
// Try to get the player from multiple sources
let player = null;
// First try: from our stored instance
if (playerInstanceRef.current && playerInstanceRef.current.player) {
player = playerInstanceRef.current.player;
}
// Second try: from global window.videojsPlayers
if (!player && typeof window !== 'undefined' && window.videojsPlayers) {
const videoId = inEmbedRef.current ? 'video-embed' : 'video-main';
player = window.videojsPlayers[videoId];
}
// Third try: from the global videojs function looking for existing players
if (!player && typeof window !== 'undefined' && window.videojs) {
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
if (videoElement && videoElement.player) {
player = videoElement.player;
}
}
// If we found a player, seek to the timestamp
if (player) {
if (timestamp >= 0 && timestamp < player.duration()) {
player.currentTime(timestamp);
} else if (timestamp >= 0) {
player.play();
}
// Scroll to the video player with smooth behavior
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
if (videoElement) {
videoElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
}
} else {
console.warn('VideoJS player not found for timestamp navigation');
}
}
};
// Add the event listener to the document for timestamp clicks
document.addEventListener('click', handleTimestampClick);
// Cleanup function
return () => {
document.removeEventListener('click', handleTimestampClick);
};
}, []); // Empty dependency array so this effect only runs once
const loadVideoJSAssets = () => {
// Check if assets are already loaded
const existingCSS = document.querySelector('link[href*="video-js.css"]');
const existingJS = document.querySelector('script[src*="video-js.js"]');
// Load CSS if not already loaded
if (!existingCSS) {
const cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = siteUrl + '/static/video_js/video-js.css';
document.head.appendChild(cssLink);
}
// Load JS if not already loaded
if (!existingJS) {
const script = document.createElement('script');
script.src = siteUrl + '/static/video_js/video-js.js';
document.head.appendChild(script);
}
};
return (
<div className="video-js-wrapper" ref={containerRef}>
{inEmbed ? <div id="video-js-root-embed" className="video-js-root-embed" /> : <div id="video-js-root-main" className="video-js-root-main" />}
</div>
);
};
VideoJSEmbed.defaultProps = {
data: {},
siteUrl: '',
};
export default VideoJSEmbed;

View File

@@ -9,9 +9,6 @@ import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/'
import { PopupMain, UserThumbnail } from '../_shared';
import { replaceString } from '../../utils/helpers/';
import './videojs-markers.js';
import './videojs.markers.css';
import { enableMarkers, addMarker } from './videojs-markers_config.js';
import { translateString } from '../../utils/helpers/';
import './Comments.scss';
@@ -457,36 +454,8 @@ export default function CommentsList(props) {
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
function setMentions(text) {
let sanitizedComment = text.split('@(_').join('<a href="/user/');
sanitizedComment = sanitizedComment.split('_)[_').join('">');
return sanitizedComment.split('_]').join('</a>');
}
function setTimestampAnchorsAndMarkers(text, videoPlayer) {
function wrapTimestampWithAnchor(match, string) {
let split = match.split(':'),
s = 0,
m = 1;
let searchParameters = new URLSearchParams(window.location.search);
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
if (MediaCMS.features.media.actions.timestampTimebar) {
addMarker(videoPlayer, s, text);
}
searchParameters.set('t', s);
const wrapped =
'<a href="' + MediaPageStore.get('media-url').split('?')[0] + '?' + searchParameters + '">' + match + '</a>';
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
function onCommentSubmit(commentId) {
onCommentsLoad();

View File

@@ -1,198 +1,203 @@
import React from 'react';
import { format } from 'timeago.js';
import { formatViewsNumber, imageExtension } from '../../../../utils/helpers/';
import { VideoPlayerByPageLink } from '../../../video-player/VideoPlayerByPageLink';
// import { VideoPlayerByPageLink } from '../../../video-player/VideoPlayerByPageLink';
import { translateString } from '../../../../utils/helpers/';
export function ItemDescription(props) {
return '' === props.description ? null : (
<div className="item-description">
<div>{props.description}</div>
</div>
);
return '' === props.description ? null : (
<div className="item-description">
<div>{props.description}</div>
</div>
);
}
export function ItemMain(props) {
return <div className="item-main">{props.children}</div>;
return <div className="item-main">{props.children}</div>;
}
export function ItemMainInLink(props) {
return (
<ItemMain>
<a className="item-content-link" href={props.link} title={props.title}>
{props.children}
</a>
</ItemMain>
);
return (
<ItemMain>
<a className="item-content-link" href={props.link} title={props.title}>
{props.children}
</a>
</ItemMain>
);
}
export function ItemTitle(props) {
return '' === props.title ? null : (
<h3>
<span aria-label={props.ariaLabel}>{props.title}</span>
</h3>
);
return '' === props.title ? null : (
<h3>
<span aria-label={props.ariaLabel}>{props.title}</span>
</h3>
);
}
export function ItemTitleLink(props) {
return '' === props.title ? null : (
<h3>
<a href={props.link} title={props.title}>
<span aria-label={props.ariaLabel}>{props.title}</span>
</a>
</h3>
);
return '' === props.title ? null : (
<h3>
<a href={props.link} title={props.title}>
<span aria-label={props.ariaLabel}>{props.title}</span>
</a>
</h3>
);
}
export function UserItemMemberSince(props) {
return <time key="member-since">Member for {format(new Date(props.date)).replace(' ago', '')}</time>;
return <time key="member-since">Member for {format(new Date(props.date)).replace(' ago', '')}</time>;
}
export function TaxonomyItemMediaCount(props) {
return (
<span key="item-media-count" className="item-media-count">
{' ' + props.count} media
</span>
);
return (
<span key="item-media-count" className="item-media-count">
{' ' + props.count} media
</span>
);
}
export function PlaylistItemMetaDate(props) {
return (
<span className="item-meta">
<span className="playlist-date">
<time dateTime={props.dateTime}>{props.text}</time>
</span>
</span>
);
return (
<span className="item-meta">
<span className="playlist-date">
<time dateTime={props.dateTime}>{props.text}</time>
</span>
</span>
);
}
export function MediaItemEditLink(props) {
let link = props.link;
let link = props.link;
if (link && window.MediaCMS.site.devEnv) {
link = '/edit-media.html';
}
if (link && window.MediaCMS.site.devEnv) {
link = '/edit-media.html';
}
return !link ? null : (
<a href={link} title={translateString("Edit media")} className="item-edit-link">
{translateString("EDIT MEDIA")}
</a>
);
return !link ? null : (
<a href={link} title={translateString('Edit media')} className="item-edit-link">
{translateString('EDIT MEDIA')}
</a>
);
}
export function MediaItemThumbnailLink(props) {
const attr = {
key: 'item-thumb',
href: props.link,
title: props.title,
tabIndex: '-1',
'aria-hidden': true,
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
};
const attr = {
key: 'item-thumb',
href: props.link,
title: props.title,
tabIndex: '-1',
'aria-hidden': true,
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
};
return (
<a {...attr}>
{!props.src ? null : (
<div key="item-type-icon" className="item-type-icon">
<div></div>
</div>
)}
</a>
);
return (
<a {...attr}>
{!props.src ? null : (
<div key="item-type-icon" className="item-type-icon">
<div></div>
</div>
)}
</a>
);
}
export function UserItemThumbnailLink(props) {
const attr = {
key: 'item-thumb',
href: props.link,
title: props.title,
tabIndex: '-1',
'aria-hidden': true,
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
};
const attr = {
key: 'item-thumb',
href: props.link,
title: props.title,
tabIndex: '-1',
'aria-hidden': true,
className: 'item-thumb' + (!props.src ? ' no-thumb' : ''),
style: !props.src ? null : { backgroundImage: "url('" + props.src + "')" },
};
return <a {...attr}></a>;
return <a {...attr}></a>;
}
export function MediaItemAuthor(props) {
return '' === props.name ? null : (
<span className="item-author">
<span>{props.name}</span>
</span>
);
return '' === props.name ? null : (
<span className="item-author">
<span>{props.name}</span>
</span>
);
}
export function MediaItemAuthorLink(props) {
return '' === props.name ? null : (
<span className="item-author">
<a href={props.link} title={props.name}>
<span>{props.name}</span>
</a>
</span>
);
return '' === props.name ? null : (
<span className="item-author">
<a href={props.link} title={props.name}>
<span>{props.name}</span>
</a>
</span>
);
}
export function MediaItemMetaViews(props) {
return (
<span className="item-views">{formatViewsNumber(props.views) + ' ' + (1 >= props.views ? translateString('view') : translateString('views'))}</span>
);
return (
<span className="item-views">
{formatViewsNumber(props.views) +
' ' +
(1 >= props.views ? translateString('view') : translateString('views'))}
</span>
);
}
export function MediaItemMetaDate(props) {
return (
<span className="item-date">
<time dateTime={props.dateTime} content={props.time}>
{props.text}
</time>
</span>
);
return (
<span className="item-date">
<time dateTime={props.dateTime} content={props.time}>
{props.text}
</time>
</span>
);
}
export function MediaItemDuration(props) {
return (
<span className="item-duration">
<span aria-label={props.ariaLabel} content={props.time}>
{props.text}
</span>
</span>
);
return (
<span className="item-duration">
<span aria-label={props.ariaLabel} content={props.time}>
{props.text}
</span>
</span>
);
}
export function MediaItemVideoPreviewer(props) {
if ('' === props.url) {
return null;
}
if ('' === props.url) {
return null;
}
const src = props.url.split('.').slice(0, -1).join('.');
const ext = imageExtension(props.url);
const src = props.url.split('.').slice(0, -1).join('.');
const ext = imageExtension(props.url);
return <span className="item-img-preview" data-src={src} data-ext={ext}></span>;
return <span className="item-img-preview" data-src={src} data-ext={ext}></span>;
}
export function MediaItemVideoPlayer(props) {
return (
<div className="item-player-wrapper">
<div className="item-player-wrapper-inner">
<VideoPlayerByPageLink pageLink={props.mediaPageLink} />
</div>
</div>
);
return (
<div className="item-player-wrapper">
<div className="item-player-wrapper-inner">
stop component tou VideoPlayerByPageLink
{/* <VideoPlayerByPageLink pageLink={props.mediaPageLink} /> */}
</div>
</div>
);
}
export function MediaItemPlaylistIndex(props) {
return (
<div className="item-order-number">
<div>
<div data-order={props.index} data-id={props.media_id}>
{props.inPlayback && props.index === props.activeIndex ? (
<i className="material-icons">play_arrow</i>
) : (
props.index
)}
return (
<div className="item-order-number">
<div>
<div data-order={props.index} data-id={props.media_id}>
{props.inPlayback && props.index === props.activeIndex ? (
<i className="material-icons">play_arrow</i>
) : (
props.index
)}
</div>
</div>
</div>
</div>
</div>
);
);
}

View File

@@ -1,6 +1,6 @@
import React, { useRef, useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import { LinksContext } from '../../utils/contexts/';
import { LinksContext, SiteConsumer } from '../../utils/contexts/';
import { PageStore, MediaPageStore } from '../../utils/stores/';
import { PageActions, MediaPageActions } from '../../utils/actions/';
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
@@ -135,7 +135,9 @@ export function MediaShareEmbed(props) {
<div className="share-embed-inner">
<div className="on-left">
<div className="media-embed-wrap">
<VideoViewer data={MediaPageStore.get('media-data')} inEmbed={true} />
<SiteConsumer>
{(site) => <VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} inEmbed={true} />}
</SiteConsumer>
</div>
</div>

View File

@@ -17,7 +17,7 @@ function shareOptionsList() {
while (i < socialMedia.length) {
switch (socialMedia[i]) {
case 'embed':
if ('video' === MediaPageStore.get('media-data').media_type) {
if ('video' === MediaPageStore.get('media-data').media_type || 'audio' === MediaPageStore.get('media-data').media_type) {
ret[socialMedia[i]] = {};
}
break;
@@ -27,66 +27,6 @@ function shareOptionsList() {
shareUrl: 'mailto:?body=' + mediaUrl,
};
break;
case 'fb':
ret[socialMedia[i]] = {
title: 'Facebook',
shareUrl: 'https://www.facebook.com/sharer.php?u=' + mediaUrl,
};
break;
case 'tw':
ret[socialMedia[i]] = {
title: 'Twitter',
shareUrl: 'https://twitter.com/intent/tweet?url=' + mediaUrl,
};
break;
case 'reddit':
ret[socialMedia[i]] = {
title: 'reddit',
shareUrl: 'https://reddit.com/submit?url=' + mediaUrl + '&title=' + mediaTitle,
};
break;
case 'tumblr':
ret[socialMedia[i]] = {
title: 'Tumblr',
shareUrl: 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + mediaUrl + '&title=' + mediaTitle,
};
break;
case 'pinterest':
ret[socialMedia[i]] = {
title: 'Pinterest',
shareUrl: 'http://pinterest.com/pin/create/link/?url=' + mediaUrl,
};
break;
case 'vk':
ret[socialMedia[i]] = {
title: 'ВКонтакте',
shareUrl: 'http://vk.com/share.php?url=' + mediaUrl + '&title=' + mediaTitle,
};
break;
case 'linkedin':
ret[socialMedia[i]] = {
title: 'LinkedIn',
shareUrl: 'https://www.linkedin.com/shareArticle?mini=true&url=' + mediaUrl,
};
break;
case 'mix':
ret[socialMedia[i]] = {
title: 'Mix',
shareUrl: 'https://mix.com/add?url=' + mediaUrl,
};
break;
case 'whatsapp':
ret[socialMedia[i]] = {
title: 'WhatsApp',
shareUrl: 'whatsapp://send?text=' + mediaUrl,
};
break;
case 'telegram':
ret[socialMedia[i]] = {
title: 'Telegram',
shareUrl: 'https://t.me/share/url?url=' + mediaUrl + '&text=' + mediaTitle,
};
break;
}
i += 1;
@@ -113,21 +53,6 @@ function ShareOptions() {
</button>
</div>
);
} else if (k === 'whatsapp') {
compList.push(
<div key={'share-' + k} className={'sh-option share-' + k}>
<a
href={shareOptions[k].shareUrl}
title=""
target="_blank"
data-action="share/whatsapp/share"
rel="noreferrer"
>
<span></span>
<span>{shareOptions[k].title}</span>
</a>
</div>
);
} else if (k === 'email') {
compList.push(
<div key="share-email" className="sh-option share-email">

View File

@@ -40,7 +40,7 @@ export function AutoPlay(props) {
<div className="auto-play">
<div className="auto-play-header">
<div className="next-label">{translateString("Up next")}</div>
<div className="auto-play-option">
{/* <div className="auto-play-option">
<label className="checkbox-label right-selectbox" tabIndex={0} onKeyPress={onKeyPress}>
{translateString("AUTOPLAY")}
<span className="checkbox-switcher-wrap">
@@ -54,7 +54,7 @@ export function AutoPlay(props) {
</span>
</span>
</label>
</div>
</div> */}
</div>
<ItemList
className="items-list-hor"

View File

@@ -495,9 +495,6 @@
color: #888;
}
.video-js {
background: none !important;
}
.vjs-big-play-button {
}
@@ -860,6 +857,7 @@
display: table;
width: 100%;
min-height: 40px;
z-index: 999;
> * {
display: table-cell;
@@ -1084,86 +1082,6 @@
}
}
.share-fb {
a,
button {
> *:first-child {
background-color: rgb(59, 89, 152);
background-image: url('../../../images/social-media-icons/fb-logo.png');
}
}
}
.share-tw {
a,
button {
> *:first-child {
background-color: rgb(29, 161, 242);
background-image: url('../../../images/social-media-icons/twitter-logo.png');
}
}
}
.share-reddit {
a,
button {
> *:first-child {
background-color: rgb(255, 69, 0);
background-image: url('../../../images/social-media-icons/reddit-logo.png');
}
}
}
.share-tumblr {
a,
button {
> *:first-child {
background-color: rgb(53, 70, 92);
background-image: url('../../../images/social-media-icons/tumblr-logo.png');
}
}
}
.share-pinterest {
a,
button {
> *:first-child {
background-color: rgb(189, 8, 28);
background-image: url('../../../images/social-media-icons/pinterest-logo.png');
}
}
}
.share-vk {
a,
button {
> *:first-child {
background-color: rgb(70, 128, 194);
background-image: url('../../../images/social-media-icons/vk-logo.png');
}
}
}
.share-linkedin {
a,
button {
> *:first-child {
background-color: rgb(0, 119, 181);
background-image: url('../../../images/social-media-icons/linkedin-logo.png');
}
}
}
.share-mix {
a,
button {
> *:first-child {
background-color: rgb(255, 130, 38);
background-image: url('../../../images/social-media-icons/mix-logo.png');
}
}
}
.share-email {
a,
button {
@@ -1172,27 +1090,6 @@
}
}
}
.share-whatsapp {
a,
button {
> *:first-child {
background-color: rgb(37, 211, 102);
background-image: url('../../../images/social-media-icons/whatsapp-logo.png');
}
}
}
.share-telegram {
a,
button {
> *:first-child {
background-color: rgb(0, 136, 204);
background-position: 11px;
background-image: url('../../../images/social-media-icons/telegram-logo.png');
}
}
}
}
.copy-field {

View File

@@ -1,83 +0,0 @@
export function extractAudioFileFormat(filename) {
let ret = null;
let ext = filename.split('.');
if (ext.length) {
ext = ext[ext.length - 1];
switch (ext) {
case 'webm':
ret = 'audio/webm';
break;
case 'flac':
ret = 'audio/flac';
break;
case 'wave':
ret = 'audio/wave';
break;
case 'wav':
ret = 'audio/wav';
break;
case 'ogg':
ret = 'audio/ogg';
break;
case 'ogg':
ret = 'audio/ogg';
break;
case 'mp3':
case 'mpeg':
ret = 'audio/mpeg';
break;
}
}
return ret;
}
// NOTE: Valid but not get used.
/*export function orderedSupportedAudioFormats( includeAll ){
let order = [];
let supports = {};
let aud = document.createElement('audio');
if(!!aud.canPlayType){
if( 'probably' === aud.canPlayType('audio/webm') || 'maybe' === aud.canPlayType('audio/webm') ){
supports.webm = !0;
order.push( 'webm' );
}
if( 'probably' === aud.canPlayType('audio/flac') || 'maybe' === aud.canPlayType('audio/flac') ){
supports.flac = !0;
order.push( 'flac' );
}
if( 'probably' === aud.canPlayType('audio/wave') || 'maybe' === aud.canPlayType('audio/wave') ){
supports.wave = !0;
order.push( 'wave' );
}
if( 'probably' === aud.canPlayType('audio/wav') || 'maybe' === aud.canPlayType('audio/wav') ){
supports.wav = !0;
order.push( 'wav' );
}
if( 'probably' === aud.canPlayType('audio/ogg') || 'maybe' === aud.canPlayType('audio/ogg') ){
supports.ogg = !0;
order.push( 'ogg' );
}
if( 'probably' === aud.canPlayType('audio/ogg; codecs="opus"') || 'maybe' === aud.canPlayType('audio/ogg; codecs="opus"') ){
supports.oggOpus = !0;
order.push( 'oggOpus' );
}
if( 'probably' === aud.canPlayType('audio/mpeg') || 'maybe' === aud.canPlayType('audio/mpeg') ){
supports.mp3 = !0;
order.push( 'mp3' );
}
}
return {
order: order,
support: supports
};
}*/

View File

@@ -1,364 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import MediaPlayer from 'mediacms-player/dist/mediacms-player.js';
import 'mediacms-player/dist/mediacms-player.css';
import { SiteContext } from '../../../utils/contexts/';
import { formatInnerLink } from '../../../utils/helpers/';
import { PageStore, MediaPageStore, VideoViewerStore as AudioPlayerStore } from '../../../utils/stores/';
import { VideoViewerActions as AudioPlayerActions } from '../../../utils/actions/';
import { UpNextLoaderView, MediaDurationInfo, PlayerRecommendedMedia } from '../../../utils/classes/';
import { extractAudioFileFormat } from './functions';
import '../VideoViewer.scss';
export default class AudioViewer extends React.PureComponent {
constructor(props) {
super(props);
let mediaData = MediaPageStore.get('media-data');
this.AudioPlayerData = {};
this.audioStartedPlaying = false;
let audioURL = formatInnerLink(mediaData.original_media_url, SiteContext._currentValue.url);
this.videoSources = [{ src: audioURL, type: extractAudioFileFormat(audioURL) }];
this.videoPoster = mediaData.poster_url;
this.videoPoster = this.videoPoster ? this.videoPoster : mediaData.thumbnail_url;
this.videoPoster = this.videoPoster ? formatInnerLink(this.videoPoster, SiteContext._currentValue.url) : '';
this.updatePlayerVolume = this.updatePlayerVolume.bind(this);
this.onAudioEnd = this.onAudioEnd.bind(this);
this.onAudioRestart = this.onAudioRestart.bind(this);
PageStore.on('switched_media_auto_play', this.onUpdateMediaAutoPlay.bind(this));
this.wrapperClick = this.wrapperClick.bind(this);
const _MediaDurationInfo = new MediaDurationInfo();
_MediaDurationInfo.update(MediaPageStore.get('media-data').duration);
this.durationISO8601 = _MediaDurationInfo.ISO8601();
}
componentDidMount() {
if (!this.videoSources.length) {
console.warn('Audio DEBUG:', "Audio file doesn't exist");
}
this.recommendedMedia = MediaPageStore.get('media-data').related_media.length
? new PlayerRecommendedMedia(
MediaPageStore.get('media-data').related_media,
this.refs.AudioElem.parentNode,
this.props.inEmbed
)
: null;
this.upNextLoaderView =
!this.props.inEmbed && MediaPageStore.get('media-data').related_media.length
? new UpNextLoaderView(MediaPageStore.get('media-data').related_media[0])
: null;
if (document.hasFocus() || 'visible' === document.visibilityState) {
this.initPlayerInstance();
} else {
this.initPlayerInstance = this.initPlayerInstance.bind(this);
window.addEventListener('focus', this.initPlayerInstance);
document.addEventListener('visibilitychange', this.initPlayerInstance);
}
}
componentWillUnmount() {
if (this.recommendedMedia) {
this.AudioPlayerData.instance.player.off('fullscreenchange', this.recommendedMedia.onResize);
PageStore.removeListener('window_resize', this.recommendedMedia.onResize);
AudioPlayerStore.removeListener('changed_viewer_mode', this.recommendedMedia.onResize);
this.recommendedMedia.destroy();
}
videojs(this.refs.AudioElem).dispose();
this.AudioPlayerData.instance = null;
delete this.AudioPlayerData.instance;
}
initPlayerInstance() {
window.removeEventListener('focus', this.initPlayerInstance);
document.removeEventListener('visibilitychange', this.initPlayerInstance);
this.refs.AudioElem.focus(); // Focus on player before instance init.
this.initPlayerInstance = null;
setTimeout(
function () {
if (!this.AudioPlayerData.instance) {
let titleLink = this.props.inEmbed ? document.createElement('a') : null;
let userThumbLink = this.props.inEmbed ? document.createElement('a') : null;
if (titleLink) {
titleLink.setAttribute('class', 'title-link');
titleLink.setAttribute('href', MediaPageStore.get('media-data').url);
titleLink.setAttribute('title', MediaPageStore.get('media-data').title);
titleLink.setAttribute('target', '_blank');
titleLink.innerHTML = MediaPageStore.get('media-data').title;
}
if (userThumbLink) {
userThumbLink.setAttribute('class', 'user-thumb-link');
userThumbLink.setAttribute('href', MediaPageStore.get('media-data').author_profile);
userThumbLink.setAttribute('title', MediaPageStore.get('media-data').author_name);
userThumbLink.setAttribute('target', '_blank');
userThumbLink.innerHTML = '<img src="' + MediaPageStore.get('media-author-thumbnail-url') + '" alt="" />';
}
let nextLink = null;
let previousLink = null;
const playlistId = this.props.inEmbed ? null : MediaPageStore.get('playlist-id');
if (playlistId) {
nextLink = MediaPageStore.get('playlist-next-media-url');
previousLink = MediaPageStore.get('playlist-previous-media-url');
} else {
nextLink =
MediaPageStore.get('media-data').related_media.length && !this.props.inEmbed
? MediaPageStore.get('media-data').related_media[0].url
: null;
}
this.AudioPlayerData.instance = new MediaPlayer(
this.refs.AudioElem,
{
sources: this.videoSources,
poster: this.videoPoster,
autoplay: !this.props.inEmbed,
bigPlayButton: true,
controlBar: {
fullscreen: false,
theaterMode: false,
next: !!nextLink,
previous: !!previousLink,
},
cornerLayers: {
topLeft: titleLink,
topRight: this.upNextLoaderView ? this.upNextLoaderView.html() : null,
bottomLeft: this.recommendedMedia ? this.recommendedMedia.html() : null,
bottomRight: userThumbLink,
},
},
{
volume: AudioPlayerStore.get('player-volume'),
soundMuted: AudioPlayerStore.get('player-sound-muted'),
},
null,
null,
this.onAudioPlayerStateUpdate.bind(this),
this.onClickNextButton.bind(this),
this.onClickPreviousButton.bind(this)
);
if (this.upNextLoaderView) {
this.upNextLoaderView.setVideoJsPlayerElem(this.AudioPlayerData.instance.player.el_);
this.onUpdateMediaAutoPlay();
}
this.refs.AudioElem.parentNode.focus(); // Focus on player.
this.AudioPlayerData.instance.player.one(
'play',
function () {
this.audioStartedPlaying = true;
}.bind(this)
);
if (this.recommendedMedia) {
this.recommendedMedia.initWrappers(this.AudioPlayerData.instance.player.el_);
this.AudioPlayerData.instance.player.one('pause', this.recommendedMedia.init);
this.AudioPlayerData.instance.player.on('fullscreenchange', this.recommendedMedia.onResize);
PageStore.on('window_resize', this.recommendedMedia.onResize);
AudioPlayerStore.on('changed_viewer_mode', this.recommendedMedia.onResize);
}
this.AudioPlayerData.instance.player.one('ended', this.onAudioEnd);
}
}.bind(this),
50
);
}
initialDocumentFocus() {
if (this.refs.AudioElem.parentNode) {
this.refs.AudioElem.parentNode.focus();
setTimeout(
function () {
this.AudioPlayerData.instance.player.play();
}.bind(this),
50
);
}
window.removeEventListener('focus', this.initialDocumentFocus);
this.initialDocumentFocus = null;
}
onClickNextButton() {
const playlistId = MediaPageStore.get('playlist-id');
let nextLink;
if (playlistId) {
nextLink = MediaPageStore.get('playlist-next-media-url');
if (null === nextLink) {
nextLink = MediaPageStore.get('media-data').related_media[0].url;
}
} else if (!this.props.inEmbed) {
nextLink = MediaPageStore.get('media-data').related_media[0].url;
}
window.location.href = nextLink;
}
onClickPreviousButton() {
const playlistId = MediaPageStore.get('playlist-id');
let previousLink;
if (playlistId) {
previousLink = MediaPageStore.get('playlist-previous-media-url');
if (null === previousLink) {
previousLink = MediaPageStore.get('media-data').related_media[0].url;
}
} else if (!this.props.inEmbed) {
previousLink = MediaPageStore.get('media-data').related_media[0].url;
}
window.location.href = previousLink;
}
onUpdateMediaAutoPlay() {
if (this.upNextLoaderView) {
if (PageStore.get('media-auto-play')) {
this.upNextLoaderView.showTimerView(this.AudioPlayerData.instance.isEnded());
} else {
this.upNextLoaderView.hideTimerView();
}
}
}
onAudioPlayerStateUpdate(newState) {
this.updatePlayerVolume(newState.volume, newState.soundMuted);
}
onAudioRestart() {
if (this.recommendedMedia) {
this.recommendedMedia.updateDisplayType('inline');
this.AudioPlayerData.instance.player.one('pause', this.recommendedMedia.init);
this.AudioPlayerData.instance.player.one('ended', this.onAudioEnd);
}
}
onAudioEnd() {
if (this.recommendedMedia) {
this.recommendedMedia.updateDisplayType('full');
this.AudioPlayerData.instance.player.one('playing', this.onAudioRestart);
}
const playlistId = this.props.inEmbed ? null : MediaPageStore.get('playlist-id');
if (playlistId) {
const moreMediaEl = document.querySelector('.video-player .more-media');
const actionsAnimEl = document.querySelector('.video-player .vjs-actions-anim');
this.upNextLoaderView.cancelTimer();
const nextMediaUrl = MediaPageStore.get('playlist-next-media-url');
if (nextMediaUrl) {
if (moreMediaEl) {
moreMediaEl.style.display = 'none';
}
if (actionsAnimEl) {
actionsAnimEl.style.display = 'none';
}
window.location.href = nextMediaUrl;
}
this.upNextLoaderView.hideTimerView();
return;
}
if (this.upNextLoaderView) {
if (PageStore.get('media-auto-play')) {
this.upNextLoaderView.startTimer();
this.AudioPlayerData.instance.player.one(
'play',
function () {
this.upNextLoaderView.cancelTimer();
}.bind(this)
);
} else {
this.upNextLoaderView.cancelTimer();
}
}
}
onUpdateMediaAutoPlay() {
if (this.upNextLoaderView) {
if (PageStore.get('media-auto-play')) {
this.upNextLoaderView.showTimerView(this.AudioPlayerData.instance.isEnded());
} else {
this.upNextLoaderView.hideTimerView();
}
}
}
updatePlayerVolume(playerVolume, playerSoundMuted) {
if (AudioPlayerStore.get('player-volume') !== playerVolume) {
AudioPlayerActions.set_player_volume(playerVolume);
}
if (AudioPlayerStore.get('player-sound-muted') !== playerSoundMuted) {
AudioPlayerActions.set_player_sound_muted(playerSoundMuted);
}
}
wrapperClick(ev) {
if (ev.target.parentNode === this.refs.videoPlayerWrap) {
if (!this.AudioPlayerData.instance.player.ended()) {
if (!this.AudioPlayerData.instance.player.hasStarted_ || this.AudioPlayerData.instance.player.paused()) {
this.AudioPlayerData.instance.player.play();
} else {
this.AudioPlayerData.instance.player.pause();
}
}
}
}
render() {
return (
<div className="player-container audio-player-container">
<div className="player-container-inner">
<div className="video-player" ref="videoPlayerWrap" onClick={this.wrapperClick}>
<audio tabIndex="1" ref="AudioElem" className="video-js vjs-mediacms native-dimensions"></audio>
</div>
</div>
</div>
);
}
}
AudioViewer.defaultProps = {
inEmbed: false,
};
AudioViewer.propTypes = {
inEmbed: PropTypes.bool,
};

View File

@@ -1,4 +0,0 @@
.player-container {
overflow: hidden;
background: #000;
}

View File

@@ -30,7 +30,7 @@
}
.page-header {
z-index: +6;
z-index: 9999;
position: fixed;
top: 0;
left: 0;

View File

@@ -1,257 +0,0 @@
import React, { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import urlParse from 'url-parse';
import MediaPlayer from 'mediacms-player/dist/mediacms-player.js';
import 'mediacms-player/dist/mediacms-player.css';
import './VideoPlayer.scss';
export function formatInnerLink(url, baseUrl) {
let link = urlParse(url, {});
if ('' === link.origin || 'null' === link.origin || !link.origin) {
link = urlParse(baseUrl + '/' + url.replace(/^\//g, ''), {});
}
return link.toString();
}
export function VideoPlayerError(props) {
return (
<div className="error-container">
<div className="error-container-inner">
<span className="icon-wrap">
<i className="material-icons">error_outline</i>
</span>
<span className="msg-wrap">{props.errorMessage}</span>
</div>
</div>
);
}
VideoPlayerError.propTypes = {
errorMessage: PropTypes.string.isRequired,
};
export function VideoPlayer(props) {
const videoElemRef = useRef(null);
let player = null;
const playerStates = {
playerVolume: props.playerVolume,
playerSoundMuted: props.playerSoundMuted,
videoQuality: props.videoQuality,
videoPlaybackSpeed: props.videoPlaybackSpeed,
inTheaterMode: props.inTheaterMode,
};
playerStates.playerVolume =
null === playerStates.playerVolume ? 1 : Math.max(Math.min(Number(playerStates.playerVolume), 1), 0);
playerStates.playerSoundMuted = null !== playerStates.playerSoundMuted ? playerStates.playerSoundMuted : !1;
playerStates.videoQuality = null !== playerStates.videoQuality ? playerStates.videoQuality : 'Auto';
playerStates.videoPlaybackSpeed = null !== playerStates.videoPlaybackSpeed ? playerStates.videoPlaybackSpeed : !1;
playerStates.inTheaterMode = null !== playerStates.inTheaterMode ? playerStates.inTheaterMode : !1;
function onClickNext() {
if (void 0 !== props.onClickNextCallback) {
props.onClickNextCallback();
}
}
function onClickPrevious() {
if (void 0 !== props.onClickPreviousCallback) {
props.onClickPreviousCallback();
}
}
function onPlayerStateUpdate(newState) {
if (playerStates.playerVolume !== newState.volume) {
playerStates.playerVolume = newState.volume;
}
if (playerStates.playerSoundMuted !== newState.soundMuted) {
playerStates.playerSoundMuted = newState.soundMuted;
}
if (playerStates.videoQuality !== newState.quality) {
playerStates.videoQuality = newState.quality;
}
if (playerStates.videoPlaybackSpeed !== newState.playbackSpeed) {
playerStates.videoPlaybackSpeed = newState.playbackSpeed;
}
if (playerStates.inTheaterMode !== newState.theaterMode) {
playerStates.inTheaterMode = newState.theaterMode;
}
if (void 0 !== props.onStateUpdateCallback) {
props.onStateUpdateCallback(newState);
}
}
function initPlayer() {
if (null !== player || null !== props.errorMessage) {
return;
}
if (!props.inEmbed) {
window.removeEventListener('focus', initPlayer);
document.removeEventListener('visibilitychange', initPlayer);
}
if (!videoElemRef.current) {
return;
}
if (!props.inEmbed) {
videoElemRef.current.focus(); // Focus on player before instance init.
}
const subtitles = {
on: false,
};
if (void 0 !== props.subtitlesInfo && null !== props.subtitlesInfo && props.subtitlesInfo.length) {
subtitles.languages = [];
let i = 0;
while (i < props.subtitlesInfo.length) {
if (
void 0 !== props.subtitlesInfo[i].src &&
void 0 !== props.subtitlesInfo[i].srclang &&
void 0 !== props.subtitlesInfo[i].label
) {
subtitles.languages.push({
src: formatInnerLink(props.subtitlesInfo[i].src, props.siteUrl),
srclang: props.subtitlesInfo[i].srclang,
label: props.subtitlesInfo[i].label,
});
}
i += 1;
}
if (subtitles.languages.length) {
subtitles.on = true;
}
}
player = new MediaPlayer(
videoElemRef.current,
{
enabledTouchControls: true,
sources: props.sources,
poster: props.poster,
autoplay: props.enableAutoplay,
bigPlayButton: true,
controlBar: {
theaterMode: props.hasTheaterMode,
pictureInPicture: false,
next: props.hasNextLink ? true : false,
previous: props.hasPreviousLink ? true : false,
},
subtitles: subtitles,
cornerLayers: props.cornerLayers,
videoPreviewThumb: props.previewSprite,
},
{
volume: playerStates.playerVolume,
soundMuted: playerStates.playerSoundMuted,
theaterMode: playerStates.inTheaterMode,
theSelectedQuality: void 0, // @note: Allow auto resolution selection by sources order.
theSelectedPlaybackSpeed: playerStates.videoPlaybackSpeed || 1,
},
props.info,
[0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
onPlayerStateUpdate,
onClickNext,
onClickPrevious
);
if (void 0 !== props.onPlayerInitCallback) {
props.onPlayerInitCallback(player, videoElemRef.current);
}
}
function unsetPlayer() {
if (null === player) {
return;
}
videojs(videoElemRef.current).dispose();
player = null;
}
useEffect(() => {
if (props.inEmbed || document.hasFocus() || 'visible' === document.visibilityState) {
initPlayer();
} else {
window.addEventListener('focus', initPlayer);
document.addEventListener('visibilitychange', initPlayer);
}
/*
// We don't need this because we have a custom function in frontend/src/static/js/components/media-viewer/VideoViewer/index.js:617
player && player.player.one('loadedmetadata', () => {
const urlParams = new URLSearchParams(window.location.search);
const paramT = Number(urlParams.get('t'));
const timestamp = !isNaN(paramT) ? paramT : 0;
player.player.currentTime(timestamp);
}); */
return () => {
unsetPlayer();
if (void 0 !== props.onUnmountCallback) {
props.onUnmountCallback();
}
};
}, []);
return null === props.errorMessage ? (
<video ref={videoElemRef} className="video-js vjs-mediacms native-dimensions"></video>
) : (
<div className="error-container">
<div className="error-container-inner">
<span className="icon-wrap">
<i className="material-icons">error_outline</i>
</span>
<span className="msg-wrap">{props.errorMessage}</span>
</div>
</div>
);
}
VideoPlayer.propTypes = {
playerVolume: PropTypes.string,
playerSoundMuted: PropTypes.bool,
videoQuality: PropTypes.string,
videoPlaybackSpeed: PropTypes.number,
inTheaterMode: PropTypes.bool,
siteId: PropTypes.string.isRequired,
siteUrl: PropTypes.string.isRequired,
errorMessage: PropTypes.string,
cornerLayers: PropTypes.object,
subtitlesInfo: PropTypes.array.isRequired,
inEmbed: PropTypes.bool.isRequired,
sources: PropTypes.array.isRequired,
info: PropTypes.object.isRequired,
enableAutoplay: PropTypes.bool.isRequired,
hasTheaterMode: PropTypes.bool.isRequired,
hasNextLink: PropTypes.bool.isRequired,
hasPreviousLink: PropTypes.bool.isRequired,
poster: PropTypes.string,
previewSprite: PropTypes.object,
onClickPreviousCallback: PropTypes.func,
onClickNextCallback: PropTypes.func,
onPlayerInitCallback: PropTypes.func,
onStateUpdateCallback: PropTypes.func,
onUnmountCallback: PropTypes.func,
};
VideoPlayer.defaultProps = {
errorMessage: null,
cornerLayers: {},
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,213 +0,0 @@
import React, { useState, useEffect, useContext } from 'react';
import PropTypes from 'prop-types';
import UrlParse from 'url-parse';
import { ApiUrlContext, SiteContext } from '../../utils/contexts/';
import { formatInnerLink, getRequest } from '../../utils/helpers/';
import { BrowserCache } from '../../utils/classes/';
import { orderedSupportedVideoFormats, videoAvailableCodecsAndResolutions, extractDefaultVideoResolution } from '../media-viewer/VideoViewer/functions';
import { VideoPlayer } from './VideoPlayer';
export function VideoPlayerByPageLink(props) {
const apiUrl = useContext(ApiUrlContext);
const site = useContext(SiteContext);
const [errorType, setErrorType] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const [videoPoster, setVideoPoster] = useState(null);
const [videoSources, setVideoSources] = useState([]);
const [videoResolutions, setVideoResolutions] = useState({});
const [subtitlesInfo, setSubtitlesInfo] = useState([]);
const [previewSprite, setPreviewSprite] = useState({});
// Keep cache data "fresh" for one day.
const browserCache = new BrowserCache(site.id, 86400);
const playerStates = {
videoQuality: browserCache.get('video-quality'),
};
playerStates.videoQuality = null !== playerStates.videoQuality ? playerStates.videoQuality : 'Auto';
let apiRequestUrl = null;
let data = null;
let videoId = null;
let urlParams = (function () {
let ret = new UrlParse(props.pageLink).query;
if (!ret) {
ret = [];
} else {
ret = ret.substring(1);
ret.split('&');
ret = ret.length ? ret.split('=') : [];
}
return ret;
})();
if (urlParams.length) {
let i = 0;
while (i < urlParams.length) {
if ('m' === urlParams[i]) {
// NOTE: "m" => media id/token.
videoId = urlParams[i + 1];
}
i += 2;
}
}
if (null !== videoId) {
apiRequestUrl = apiUrl.media + '/' + videoId;
}
function onApiResponse(response) {
if (void 0 === response || !!!response || void 0 === response.data || !!!response.data) {
return;
}
data = response.data;
let srcUrl, k;
const videoSources = [];
let videoPoster = null;
let videoInfo = videoAvailableCodecsAndResolutions(data.encodings_info, data.hls_info);
let errorType = null;
let errorMessage = null;
if ('string' === typeof data.poster_url) {
videoPoster = formatInnerLink(data.poster_url, site.url);
} else if ('string' === typeof data.thumbnail_url) {
videoPoster = formatInnerLink(data.thumbnail_url, site.url);
}
const resolutionsKeys = Object.keys(videoInfo);
if (!resolutionsKeys.length) {
videoInfo = null;
} else {
const supportedFormats = orderedSupportedVideoFormats();
let defaultResolution = playerStates.videoQuality;
if (null === defaultResolution || ('Auto' === defaultResolution && void 0 === videoInfo['Auto'])) {
defaultResolution = 720; // Default resolution.
}
let defaultVideoResolution = extractDefaultVideoResolution(defaultResolution, videoInfo);
if ('Auto' === defaultResolution && void 0 !== videoInfo['Auto']) {
videoSources.push({ src: videoInfo['Auto'].url[0] });
}
k = 0;
while (k < videoInfo[defaultVideoResolution].format.length) {
if ('hls' === videoInfo[defaultVideoResolution].format[k]) {
videoSources.push({ src: videoInfo[defaultVideoResolution].url[k] });
break;
}
k += 1;
}
for (k in data.encodings_info[defaultVideoResolution]) {
if (data.encodings_info[defaultVideoResolution].hasOwnProperty(k)) {
if (supportedFormats.support[k]) {
srcUrl = data.encodings_info[defaultVideoResolution][k].url;
if (!!srcUrl) {
// NOTE: In some cases, url value is 'null'.
srcUrl = formatInnerLink(srcUrl, site.url);
videoSources.push({
src: srcUrl,
encodings_status: data.encodings_info[defaultVideoResolution][k].status,
});
}
}
}
}
}
if (1 === videoSources.length && 'running' === videoSources[0].encodings_status) {
errorType = 'encodingRunning';
errorMessage = 'Media encoding is currently running. Try again in few minutes.';
}
if (null !== errorType) {
switch (errorType) {
case 'encodingRunning':
case 'encodingPending':
case 'encodingFailed':
break;
default:
console.warn('VIDEO DEBUG:', "Video files don't exist");
}
}
setErrorType(errorType);
setErrorMessage(errorMessage);
setVideoPoster(videoPoster);
setVideoSources(videoSources);
setVideoResolutions(videoInfo);
setSubtitlesInfo(data.subtitles_info);
setPreviewSprite(
!!data.sprites_url
? { url: formatInnerLink(data.sprites_url, site.url), frame: { width: 160, height: 90, seconds: 10 } }
: null
);
const featuredItemDescrContent = document.querySelector('.feat-first-item .item .item-description > div');
if (featuredItemDescrContent) {
// featuredItemDescrContent.innerHTML = data.summary + '<br/><br/>' + data.description;
featuredItemDescrContent.innerHTML = data.summary;
}
}
function onApiResponseFail(response) {
if (void 0 === response || void 0 === response.type) {
return;
}
switch (response.type) {
case 'network':
case 'private':
case 'unavailable':
setErrorType(response.type);
setErrorMessage(
void 0 !== response.message ? response.message : "Αn error occurred while loading the media's data"
);
break;
}
}
useEffect(() => {
if (null !== apiRequestUrl) {
getRequest(apiRequestUrl, false, onApiResponse, onApiResponseFail);
}
}, []);
return videoSources.length ? (
<div className="video-player">
<VideoPlayer
siteId={site.id}
siteUrl={site.url}
info={videoResolutions}
sources={videoSources}
poster={videoPoster}
previewSprite={previewSprite}
subtitlesInfo={subtitlesInfo}
enableAutoplay={false}
inEmbed={false}
hasTheaterMode={false}
hasNextLink={false}
hasPreviousLink={false}
errorMessage={errorMessage}
/>
</div>
) : null;
}
VideoPlayerByPageLink.propTypes = {
pageLink: PropTypes.string.isRequired,
};

View File

@@ -56,11 +56,12 @@ export const EmbedPage: React.FC = () => {
</div>
</div>
)}
{loadedVideo && (
<SiteConsumer>
{(site) => (
{(site) => (
<VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} containerStyles={containerStyles} />
)}
)}
</SiteConsumer>
)}
</div>

View File

@@ -1,13 +0,0 @@
import React from 'react';
import AudioViewer from '../components/media-viewer/AudioViewer';
import { _MediaPage } from './_MediaPage';
export class MediaAudioPage extends _MediaPage {
viewerContainerContent() {
return <AudioViewer />;
}
mediaType() {
return 'audio';
}
}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { SiteConsumer } from '../utils/contexts/';
import { MediaPageStore } from '../utils/stores/';
import AttachmentViewer from '../components/media-viewer/AttachmentViewer';
import AudioViewer from '../components/media-viewer/AudioViewer';
// import AudioViewer from '../components/media-viewer/AudioViewer';
import ImageViewer from '../components/media-viewer/ImageViewer';
import PdfViewer from '../components/media-viewer/PdfViewer';
import VideoViewer from '../components/media-viewer/VideoViewer';
@@ -46,11 +46,10 @@ export class MediaPage extends _VideoMediaPage {
viewerContainerContent(mediaData) {
switch (MediaPageStore.get('media-type')) {
case 'video':
case 'audio':
return (
<SiteConsumer>{(site) => <VideoViewer data={mediaData} siteUrl={site.url} inEmbed={!1} />}</SiteConsumer>
);
case 'audio':
return <AudioViewer />;
case 'image':
return <ImageViewer />;
case 'pdf':

View File

@@ -5,6 +5,7 @@ import { _VideoMediaPage } from './_VideoMediaPage';
export class MediaVideoPage extends _VideoMediaPage {
viewerContainerContent(mediaData) {
return <>Not working anymore?</>; // TODO: check this if this page not working anymore as MediaPage.js do the same work
return <SiteConsumer>{(site) => <VideoViewer data={mediaData} siteUrl={site.url} inEmbed={!1} />}</SiteConsumer>;
}

View File

@@ -54,7 +54,7 @@ export class _VideoMediaPage extends Page {
}
onMediaLoad() {
const isVideoMedia = 'video' === MediaPageStore.get('media-type');
const isVideoMedia = 'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type');
if (isVideoMedia) {
this.onViewerModeChange = this.onViewerModeChange.bind(this);

View File

@@ -1,467 +0,0 @@
import { formatViewsNumber, quickSort, greaterCommonDivision, addClassname, removeClassname } from '../helpers/';
import ItemsInlineSlider from '../../components/item-list/includes/itemLists/ItemsInlineSlider';
import { MediaDurationInfo } from './MediaDurationInfo';
const _MediaDurationInfo = new MediaDurationInfo();
function itemDuration(duration) {
return '<span class="more-media-duration"><span>' + duration + '</span></span>';
}
function itemThumb(img, duration) {
return img
? '<span class="more-media-item-thumb" style="background-image:url(\'' +
img +
'\');">' +
itemDuration(duration) +
'</span>'
: '';
}
function itemTitle(title) {
return '<span class="more-media-title">' + title + '</span>';
}
function itemAuthor(author) {
return '<span class="more-media-author">' + author + '</span>';
}
function itemViews(views, hideViews) {
return hideViews ? '' : '<span class="more-media-views">' + views + '</span>';
}
function itemMeta(author, views, hideViews) {
return '<span class="more-media-meta">' + itemAuthor(author) + itemViews(views, hideViews) + '</span>';
}
function itemContent(title, author, views, hideViews) {
return '<span class="more-media-item-content">' + itemTitle(title) + itemMeta(author, views, hideViews) + '</span>';
}
function generateRatiosGrids(columnsArr, rowsArr, itemsLength, viewportWidth, viewportHeight) {
let i = 0,
j,
rw,
rh,
gcd,
ret = {};
let defaultRatioWidth = 16;
let defaultRatioheight = 9;
while (i < columnsArr.length) {
j = 0;
while (j < rowsArr.length) {
rw = defaultRatioWidth * columnsArr[i];
rh = defaultRatioheight * rowsArr[j];
gcd = greaterCommonDivision(rw, rh);
if (1 < gcd) {
rw = rw / gcd;
rh = rh / gcd;
}
if (columnsArr[i] * (rowsArr[j] - 1) < itemsLength) {
ret[rw + '/' + rh] = ret[rw + '/' + rh] || { val: rw / rh, grid: [] };
ret[rw + '/' + rh].grid.push([columnsArr[i], rowsArr[j]]);
}
j += 1;
}
i += 1;
}
return ret;
}
function matchedRatioGrids(ratios, ww, wh, pw, ph, pr, itemWidthBreakpoint) {
let dist = [],
pntr = {},
ret = [];
let x, k, availableGrids;
if (3 * itemWidthBreakpoint <= pw) {
ret = 1.6 < pr ? [4, 3] : [3, 4];
} else if (1.5 * itemWidthBreakpoint >= pw) {
if (160 >= ph) {
ret = [1, 1];
} else if (320 >= ph) {
ret = [1, 2];
} else if (480 >= ph) {
ret = [1, 3];
} else if (640 >= ph) {
ret = [1, 4];
} else if (800 >= ph) {
ret = [1, 6];
} else {
ret = [1, 6];
}
} else if (2.5 * itemWidthBreakpoint >= pw) {
ret = [2, 1];
if (160 >= ph) {
ret = [2, 1];
} else if (320 >= ph) {
ret = [2, 2];
} else if (480 >= ph) {
ret = [2, 3];
} else if (640 >= ph) {
ret = [2, 4];
} else if (800 >= ph) {
ret = [2, 5];
} else {
ret = [2, 6];
}
}
if (!ret.length) {
for (k in ratios) {
if (ratios.hasOwnProperty(k)) {
x = Math.abs(pr - ratios[k].val);
dist.push(x);
pntr[x] = k;
}
}
availableGrids = ratios[pntr[quickSort(dist, 0, dist.length - 1)[0]]].grid;
if (1 < availableGrids.length) {
dist = [];
pntr = {};
k = 0;
while (k < availableGrids.length) {
x = Math.abs(pw - availableGrids[k][0] * itemWidthBreakpoint);
dist.push(x);
pntr[x] = k;
k += 1;
}
ret = availableGrids[pntr[quickSort(dist, 0, dist.length - 1)[0]]];
} else {
ret = availableGrids[0];
}
if (2 * itemWidthBreakpoint >= pw) {
ret[0] = Math.min(2, ret[0]);
} else if (3 * itemWidthBreakpoint >= pw) {
ret[0] = Math.min(3, ret[0]);
}
if (390 >= ph) {
ret[1] = Math.min(2, ret[1]);
} else if (590 >= ph) {
ret[1] = Math.min(3, ret[1]);
}
}
return ret;
}
function gridClassname(itemsLength, container) {
if (!itemsLength || !container || !container.firstChild) {
return '';
}
const ww = window.outerWidth;
const wh = window.outerHeight;
const child = container.firstChild;
const pw = child.offsetWidth;
const ph = child.offsetHeight;
const pr = pw / ph;
let ret = matchedRatioGrids(
generateRatiosGrids([1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6], itemsLength, parseInt(ww, 10), parseInt(wh, 10)),
ww,
wh,
pw,
ph,
pr,
250
);
return ret.length ? ' grid-col-' + ret[0] + ' grid-row-' + ret[1] : '';
}
function buildItemsElements(itemsData, items, wrapper, inEmbed, hideViews) {
let i = 0;
while (i < itemsData.length) {
_MediaDurationInfo.update(itemsData[i].duration);
items[i] = document.createElement('div');
items[i].setAttribute('class', 'more-media-item before-more-media-item-load');
items[i].setAttribute('style', '--n: ' + i);
items[i].innerHTML =
'<a href="' +
itemsData[i].url +
'" title="' +
itemsData[i].title +
'"' +
(inEmbed ? 'target="_blank"' : '') +
'>' +
itemThumb(itemsData[i].thumbnail_url, _MediaDurationInfo.toString()) +
itemContent(
itemsData[i].title,
itemsData[i].author_name,
formatViewsNumber(itemsData[i].views) + ' ' + (1 >= itemsData[i].views ? 'view' : 'views'),
hideViews
) +
'</a>';
wrapper.appendChild(items[i]);
i += 1;
}
}
export function PlayerRecommendedMedia(itemsData, inEmbed, hideViews) {
inEmbed = inEmbed || false;
let container = null;
function updateSlider(afterItemsUpdate) {
if (!inlineSlider) {
if (!domElems.contentInner.offsetWidth) {
return;
}
inlineSlider = new ItemsInlineSlider(domElems.contentInner, '.more-media-item');
disableItemsRevealAnim();
}
inlineSlider.updateDataState(itemsData.length, true, true);
updateSliderButtonsView();
}
function disableItemsRevealAnim() {
setTimeout(function () {
let i = 0;
while (i < domElems.items.length) {
domElems.items[i].setAttribute('class', 'more-media-item');
domElems.items[i].setAttribute('style', null);
i += 1;
}
}, domElems.items.length * 75 + 200); // NOTE: 200ms is reveal animation duration and 75ms is reveal animation delay (in CSS, with class selector '.before-more-media-item-load' ).
}
function updateSliderButtonsView() {
domElems.prevSlide.style.display = inlineSlider.hasPreviousSlide() ? '' : 'none';
domElems.nextSlide.style.display = inlineSlider.hasNextSlide() ? '' : 'none';
}
function toggleInlineVisibility(ev) {
ev.preventDefault();
ev.stopPropagation();
state.openInlineMoreMedia = !state.openInlineMoreMedia;
(state.openInlineMoreMedia ? removeClassname : addClassname)(domElems.wrapper, 'hidden-inline-more-media');
updateSlider(false);
}
function clickPreviousBtn(ev) {
ev.preventDefault();
ev.stopPropagation();
inlineSlider.previousSlide();
updateSliderButtonsView();
inlineSlider.scrollToCurrentSlide();
}
function clickNextBtn(ev) {
ev.preventDefault();
ev.stopPropagation();
inlineSlider.nextSlide();
updateSliderButtonsView();
inlineSlider.scrollToCurrentSlide();
}
function wrapperClassname() {
if (null === container) {
return;
}
let ret = 'more-media';
switch (state.displayType) {
case 'full':
ret += ' full-wrapper';
break;
case 'inline-small':
ret += ' inline-slider-small';
break;
case 'inline':
ret += ' inline-slider';
break;
}
ret += state.openInlineMoreMedia ? '' : ' hidden-inline-more-media';
return ret + gridClassname(itemsData.length, container);
}
function updateWrapperParentStyle() {
switch (state.displayType) {
case 'full':
domElems.wrapper.parentNode.style.top = '';
break;
case 'inline-small':
case 'inline':
domElems.wrapper.parentNode.style.top = 'auto';
break;
}
}
function updateWrapperClassname() {
domElems.wrapper.setAttribute('class', wrapperClassname());
}
let inlineSlider;
const domElems = {
wrapper: document.createElement('div'),
title: document.createElement('h2'),
openBtn: document.createElement('button'),
closeBtn: document.createElement('button'),
prevSlide: document.createElement('div'),
nextSlide: document.createElement('div'),
prevSlideBtn: null,
nextSlideBtn: null,
content: document.createElement('div'),
contentInner: document.createElement('div'),
items: [],
};
const state = {
isInited: false,
displayType: 'inline',
openInlineMoreMedia: true,
};
domElems.title.innerHTML = 'More videos';
domElems.openBtn.innerHTML = 'More videos';
domElems.prevSlide.innerHTML =
'<button class="circle-icon-button"><span><span><i class="vjs-icon-navigate-before"></i></span></span></button>';
domElems.nextSlide.innerHTML =
'<button class="circle-icon-button"><span><span><i class="vjs-icon-navigate-next"></i></span></span></button>';
domElems.title.setAttribute('class', 'more-media-wrap-title');
domElems.openBtn.setAttribute('class', 'open-more-videos');
domElems.closeBtn.setAttribute('class', 'close-more-videos vjs-icon-close');
domElems.prevSlide.setAttribute('class', 'prev-slide');
domElems.nextSlide.setAttribute('class', 'next-slide');
domElems.content.appendChild(domElems.contentInner);
domElems.wrapper.appendChild(domElems.title);
domElems.wrapper.appendChild(domElems.openBtn);
domElems.wrapper.appendChild(domElems.closeBtn);
domElems.wrapper.appendChild(domElems.content);
domElems.content.appendChild(domElems.prevSlide);
domElems.content.appendChild(domElems.nextSlide);
domElems.prevSlideBtn = domElems.prevSlide.querySelector('button');
domElems.nextSlideBtn = domElems.nextSlide.querySelector('button');
function bindEvents() {
if (domElems.prevSlideBtn) {
domElems.prevSlideBtn.addEventListener('click', clickPreviousBtn);
}
if (domElems.nextSlideBtn) {
domElems.nextSlideBtn.addEventListener('click', clickNextBtn);
}
domElems.openBtn.addEventListener('click', toggleInlineVisibility);
domElems.closeBtn.addEventListener('click', toggleInlineVisibility);
}
function unbindEvents() {
if (domElems.prevSlideBtn) {
domElems.prevSlideBtn.removeEventListener('click', clickPreviousBtn);
}
if (domElems.nextSlideBtn) {
domElems.nextSlideBtn.removeEventListener('click', clickNextBtn);
}
domElems.openBtn.removeEventListener('click', toggleInlineVisibility);
domElems.closeBtn.removeEventListener('click', toggleInlineVisibility);
}
this.html = function () {
return domElems.wrapper;
};
this.onResize = function () {
updateWrapperClassname();
switch (state.displayType) {
case 'inline':
updateSlider(false);
break;
}
};
this.initWrappers = function (wrapper) {
container = wrapper;
updateWrapperParentStyle();
updateWrapperClassname();
};
this.init = function () {
if (!state.itemsAreBuilt) {
state.itemsAreBuilt = true;
buildItemsElements(itemsData, domElems.items, domElems.contentInner, inEmbed, hideViews);
}
switch (state.displayType) {
case 'inline':
if (inlineSlider) {
updateSlider(false);
disableItemsRevealAnim();
} else {
updateSlider(true);
}
break;
}
};
this.destroy = function () {
unbindEvents();
};
this.updateDisplayType = function (type) {
let newType, i;
switch (type) {
case 'full':
case 'inline':
case 'inline-small':
newType = type;
break;
}
if (newType && newType !== state.displayType) {
state.displayType = newType;
updateWrapperParentStyle();
updateWrapperClassname();
i = 0;
while (i < domElems.items.length) {
domElems.items[i].setAttribute('class', 'more-media-item before-more-media-item-load');
domElems.items[i].setAttribute('style', '--n: ' + i);
i += 1;
}
switch (newType) {
case 'full':
disableItemsRevealAnim();
break;
}
}
};
bindEvents();
}

View File

@@ -1,4 +1,3 @@
export * from './BrowserCache';
export * from './MediaDurationInfo';
export * from './PlayerRecommendedMedia';
export * from './UpNextLoaderView';

View File

@@ -30,16 +30,6 @@ export function init(item, shareOptions) {
if (void 0 !== shareOptions) {
const validShareOptions = [
'embed',
'fb',
'tw',
'whatsapp',
'telegram',
'reddit',
'tumblr',
'vk',
'pinterest',
'mix',
'linkedin',
'email',
];

View File

@@ -6,6 +6,7 @@ export function init(settings) {
url: '',
api: '',
title: '',
useRoundedCorners: true,
};
if (void 0 !== settings) {
@@ -24,6 +25,10 @@ export function init(settings) {
if ('string' === typeof settings.title) {
SITE.title = settings.title.trim();
}
if ('boolean' === typeof settings.useRoundedCorners) {
SITE.useRoundedCorners = settings.useRoundedCorners;
}
}
}

View File

@@ -185,6 +185,8 @@ class MediaPageStore extends EventEmitter {
switch (this.get('media-type')) {
case 'video':
case 'audio':
this.emit('loaded_video_data');
break;
case 'image':
this.emit('loaded_' + this.get('media-type') + '_data');
break;
@@ -607,7 +609,7 @@ class MediaPageStore extends EventEmitter {
}
isVideo() {
return 'video' === this.get('media-type');
return 'video' === this.get('media-type') || 'audio' === this.get('media-type');
}
onPlaylistCreationCompleted(response) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long