mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-23 06:43:51 -05:00
feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements
This commit is contained in:
committed by
GitHub
parent
b39072c8ae
commit
a5e6e7b9ca
231
frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx
Normal file
231
frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx
Normal 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;
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}*/
|
||||
@@ -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,
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
.player-container {
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@
|
||||
}
|
||||
|
||||
.page-header {
|
||||
z-index: +6;
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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':
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './BrowserCache';
|
||||
export * from './MediaDurationInfo';
|
||||
export * from './PlayerRecommendedMedia';
|
||||
export * from './UpNextLoaderView';
|
||||
@@ -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',
|
||||
];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user