From 56f7519fb49c291e2c687b5d2c3b41655843ee25 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:33:15 +0900 Subject: [PATCH 01/91] Create index.js --- src/components/external-video-player/index.js | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/components/external-video-player/index.js diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js new file mode 100644 index 00000000..5bb6219c --- /dev/null +++ b/src/components/external-video-player/index.js @@ -0,0 +1,223 @@ +import React, { Component } from 'react'; +import ReactPlayer from 'react-player'; +import cx from 'classnames'; +import { defineMessages } from 'react-intl'; +import logger from 'utils/logger'; +import { ID } from 'utils/constants'; +import { getCurrentDataIndex } from 'utils/data'; + +import './styles.css'; + + +const intlMessages = defineMessages({ + autoPlayWarning: { + id: 'player.externalVideo.autoPlayWarning', + description: 'Shown when user needs to interact with player to make it work', + }, + +}); + + +const SYNC_INTERVAL_SECOND = 5; +const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; +const ORCHESTRATOR_INTERVAL_MILLISECOND = 500; + +class ExternalVideoPlayer extends Component { + + constructor(props) { + super(props); + + this.player = null; + + this.autoPlayTimeout = null; + + this.hasPlayedBefore = false; + this.playerIsReady = false; + + this.time = 0; + this.buffering= false; + this.lastTime = 0; + this.playerUpdateTime = -1; + this.primaryPlayerPlaying = false; + this.lastEventPlaybackRate = 1; + + this.state = { + muted: false, + playing: false, + autoPlayBlocked: false, + errorPlaying: false, + playbackRate: 1, + volume: 1, + }; + + this.opts = { + // default option for all players, can be overwritten + playerOptions: { + autoplay: false, + playsinline: true, + controls: false, + }, + file: { + attributes: { + controls: false, + autoPlay: false, + playsInline: true, + }, + }, + youtube: { + playerVars: { + autoplay: 0, + modestbranding: 1, + autohide: 1, + rel: 0, + ecver: 2, + controls: 0, + enablejsapi: 0, + showinfo: 0 + }, + }, + + preload: true, + }; + + + this.getCurrentTime = this.getCurrentTime.bind(this); + this.setPlaybackRate = this.setPlaybackRate.bind(this); + this.seekTo = this.seekTo.bind(this); + + this.handleFirstPlay = this.handleFirstPlay.bind(this); + this.handleOnReady = this.handleOnReady.bind(this); + this.handleOnPlay = this.handleOnPlay.bind(this); + this.handleOnPause = this.handleOnPause.bind(this); + this.handleVolumeChange = this.handleVolumeChange.bind(this); + this.handleOnBuffer = this.handleOnBuffer.bind(this); + this.handleOnBufferEnd = this.handleOnBufferEnd.bind(this); + + this.orchestrator = this.orchestrator.bind(this); + this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); + + } + + autoPlayBlockDetected() { + this.setState({ autoPlayBlocked: true }); + } + + handleFirstPlay() { + const { hasPlayedBefore } = this; + + if (!hasPlayedBefore) { + this.hasPlayedBefore = true; + + this.setState({ autoPlayBlocked: false }); + + if (this.autoPlayTimeout) { + clearTimeout(this.autoPlayTimeout); + } + + } + } + + getCurrentTime() { + if (this.player && this.player.getCurrentTime) { + return Math.round(this.player.getCurrentTime()); + } + } + + + setPlaybackRate() { + + const { primaryPlaybackRate } = this.props; + + // Rate depends on primary rate player + const rate = primaryPlaybackRate * this.lastEventPlaybackRate; + + const currentRate = this.state.playbackRate; + + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${primaryPlaybackRate} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); + + if (currentRate === rate) { + return; + } + + this.setState({ playbackRate: rate }); + + } + + handleOnReady() { + const { hasPlayedBefore, playerIsReady } = this; + + if (hasPlayedBefore || playerIsReady) { + return; + } + + this.playerIsReady = true; + this.handleFirstPlay(); + + const { onPlayerReady } = this.props; + + if (onPlayerReady) onPlayerReady(ID.EXTERNAL_VIDEOS, this); + + + } + + handleOnPlay() { + const { playing } = this.state; + + if (!playing && this.primaryPlayerPlaying) { + this.setState({ playing: true }); + this.handleFirstPlay(); + } + } + + handleOnPause() { + const { playing } = this.state; + + if (playing) { + this.setState({ playing: false }); + this.handleFirstPlay(); + } + } + + handleOnBuffer() { + this.buffering = true; + } + + handleOnBufferEnd() { + this.buffering = false; + } + + handleVolumeChange = (value, isMuted) => { + this.setState({ volume: parseFloat(value)}); + this.setState({ muted: isMuted}); + } + + + seekTo(time) { + const { player } = this; + + if (!player) { + //return logger.error('No player on seek'); + return; + } + + // Seek if viewer has drifted too far away from presenter + if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + player.seekTo(time, true); + } + } + + componentDidMount () { + this.timer = setInterval(() => this.orchestrator(), ORCHESTRATOR_INTERVAL_MILLISECOND); + } + + componentWillUnmount () { + clearInterval(this.timer); + } + + orchestrator () { + const { events, active, getCurrentPlayerTime } = this.props; + const { playing, playbackRate } = this.state; + + this.time = getCurrentPlayerTime(); + + let primaryPlayerPlaying = true; From 9e5764528d0d02bc0862f38eb5110a29f3ea8c62 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:35:19 +0900 Subject: [PATCH 02/91] Update index.js --- src/components/external-video-player/index.js | 101 +++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 5bb6219c..2a71efd8 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -215,9 +215,104 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, getCurrentPlayerTime } = this.props; + const { events, active, primaryPlaybackRate } = this.props; const { playing, playbackRate } = this.state; - this.time = getCurrentPlayerTime(); - let primaryPlayerPlaying = true; + + if (this.time === this.lastTime) { + primaryPlayerPlaying = false; + } + + this.lastTime = this.time; + this.primaryPlayerPlaying = primaryPlayerPlaying; + + if (active && !this.hasPlayedBefore && !this.autoPlayTimeout) { + this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); + } + + const index = getCurrentDataIndex(events, this.time); + + logger.debug(`external_video: player time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + + + if (!primaryPlayerPlaying || !active) { + this.handleOnPause(); + this.playerUpdateTime = -1; + return + } + + if (index && events && events[index] && events[index].type) + { + const {type, time, rate, playing} = events[index]; + + logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); + + switch (type) { + case "stop": + this.handleOnPause(); + break; + case "play": + this.handleOnPlay(); + break; + case "playerUpdate": + if (this.playerUpdateTime !== time) { + this.lastEventPlaybackRate=rate; + this.seekTo(time); + playing ? this.handleOnPlay() : this.handleOnPause() + this.playerUpdateTime=time; + } + break; + default: + ; + } + } + + this.setPlaybackRate(); + } + + + render() { + + const { videoUrl, active, intl } = this.props; + const { playing, playbackRate, muted, autoPlayBlocked, volume } = this.state; + + return ( + +
{ this.playerParent = ref; }} + > + {autoPlayBlocked + ? ( +

+ {intl.formatMessage(intlMessages.autoPlayWarning)} +

+ ) + : '' + } + + { this.player = ref; }} + width="100%" + height="100%" + /> + +
+ ); + + } +} + +export default (ExternalVideoPlayer); From 661e27b596c082e9c56c1e1b065971fc023ec2e3 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:35:53 +0900 Subject: [PATCH 03/91] Create styles.css --- .../external-video-player/styles.css | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/components/external-video-player/styles.css diff --git a/src/components/external-video-player/styles.css b/src/components/external-video-player/styles.css new file mode 100644 index 00000000..11cd3c00 --- /dev/null +++ b/src/components/external-video-player/styles.css @@ -0,0 +1,21 @@ +.autoPlayWarning { + position: absolute; + z-index: 100; + font-size: x-large; + color: white; + width: 100%; + background-color: rgba(6,23,42,0.5); + bottom: 20%; + vertical-align: middle; + text-align: center; + pointer-events: none; +} + +.externalVideos-wrapper { + display: flex; + position: absolute; + height: 100%; + position: absolute; + width: 95%; + left: 2.5%; +} From b7dde04bc0ef3755dec0a19f60ec28596a1a7aed Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:11:30 +0900 Subject: [PATCH 04/91] Update index.js --- src/components/player/content/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index d1c9d5a4..5a36fa61 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -3,6 +3,7 @@ import cx from 'classnames'; import Presentation from 'components/presentation'; import TldrawPresentation from 'components/tldraw'; import Screenshare from 'components/screenshare'; +import ExternalVideoPlayer from 'components/external-video-player'; import Thumbnails from 'components/thumbnails'; import FullscreenButton from 'components/player/buttons/fullscreen'; import { LAYOUT } from 'utils/constants'; @@ -35,6 +36,7 @@ const Content = ({
{isTldrawWhiteboard ? : } {layout.screenshare ? : null} + {layout.externalVideo ? : null}
Date: Sun, 18 Jun 2023 23:15:55 +0900 Subject: [PATCH 05/91] Update image.js --- src/components/thumbnails/image.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/thumbnails/image.js b/src/components/thumbnails/image.js index 04314aae..3849a268 100644 --- a/src/components/thumbnails/image.js +++ b/src/components/thumbnails/image.js @@ -29,6 +29,16 @@ const Image = ({
); } + + const external_video = src === ID.EXTERNAL_VIDEOS; + + if (external_video) { + return ( +
+ +
+ ); + } const logo = src.includes('logo'); From 13344c3b9d0421eab9c45f43318121018d6b10f5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:16:59 +0900 Subject: [PATCH 06/91] Update index.scss --- src/components/thumbnails/index.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/thumbnails/index.scss b/src/components/thumbnails/index.scss index f46413e5..5314f422 100644 --- a/src/components/thumbnails/index.scss +++ b/src/components/thumbnails/index.scss @@ -41,5 +41,10 @@ $thumbnail-height: calc((#{$bottom-content-height} / 10) * 6); font-size: xxx-large; font-weight: var(--font-weight-semi-bold); } + + .external_video { + font-size: xxx-large; + font-weight: var(--font-weight-semi-bold); + } } } From 95241f28cbcfea502cccf3ee473f797080a99c14 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:19:01 +0900 Subject: [PATCH 07/91] Update config.js --- src/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.js b/src/config.js index 83383b6d..4c9be99e 100644 --- a/src/config.js +++ b/src/config.js @@ -27,6 +27,7 @@ const files = { shapes: 'shapes.svg', tldraw: 'tldraw.json', videos: 'external_videos.json', + externalVideos: 'external_videos.xml', }; const locale = { default: 'en' }; From b177cbcda1f5cffe4999d445921f60d8669dfc1d Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:21:10 +0900 Subject: [PATCH 08/91] Update en.json --- src/locales/messages/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/locales/messages/en.json b/src/locales/messages/en.json index ed46468b..d4aa53dc 100644 --- a/src/locales/messages/en.json +++ b/src/locales/messages/en.json @@ -39,4 +39,6 @@ "player.search.modal.subtitle": "Find presentation slides content", "player.thumbnails.wrapper.aria": "Thumbnails area", "player.webcams.wrapper.aria": "Webcams area" + "player.video.wrapper.aria": "Video area", + "player.externalVideo.autoPlayWarning": "We need your permission for playing audio" } From a79e329e7b9e52d142f8386becffd5428b181e13 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:27:39 +0900 Subject: [PATCH 09/91] Update builder.js --- src/utils/builder.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/utils/builder.js b/src/utils/builder.js index 9d778b63..fffb5549 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -221,6 +221,12 @@ const buildThumbnails = slides => { src: ID.SCREENSHARE, timestamp, }); + } else if (src.includes(ID.EXTERNAL_VIDEOS)) { + result.push({ + id, + src: ID.EXTERNAL_VIDEOS, + timestamp, + }); } else { result.push({ id, @@ -487,6 +493,34 @@ const buildScreenshare = result => { return data; }; +const buildExternalVideos = result => { + let data = []; + const { recording } = result; + + if (hasProperty(recording, 'video')) { + data = recording.video.map(video => { + const attr = getAttr(video); + return { + timestamp: parseFloat(attr.start_timestamp), + clear: parseFloat(attr.stop_timestamp), + url: attr.url, + events: video.event.map(event => { + const attr = getAttr(event); + return { + timestamp: parseFloat(attr.timestamp), + type: attr.type, + time: attr.time, + rate: parseFloat(attr.rate), + playing: (attr.playing === 'true'), + } + }) + }; + }); + } + + return data; +}; + const build = (filename, value) => { return new Promise((resolve, reject) => { let data; @@ -553,6 +587,9 @@ const build = (filename, value) => { case config.screenshare: data = buildScreenshare(result); break; + case config.externalVideos: + data = buildExternalVideos(result); + break; case config.shapes: data = buildShapes(result); break; From 4889628bd7d3c540fa5915f1bab6e9f971427645 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:33:28 +0900 Subject: [PATCH 10/91] Update index.js --- src/utils/data/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/utils/data/index.js b/src/utils/data/index.js index 4d966bdb..98a62dc4 100644 --- a/src/utils/data/index.js +++ b/src/utils/data/index.js @@ -66,10 +66,17 @@ const getBar = (percentage) => { const getCurrentContent = (time) => { const { SCREENSHARE, + EXTERNAL_VIDEOS, PRESENTATION, } = ID; + + let content=PRESENTATION; - const content = isEnabled(storage.screenshare, time) ? SCREENSHARE : PRESENTATION; + if (isEnabled(screenshare, time)) { + content=SCREENSHARE; + } else if (isEnabled(externalVideos, time)) { + content=EXTERNAL_VIDEOS; + } return content; }; From f53c53adda0ff031e289d3530e0bed7f6f98fa03 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:36:57 +0900 Subject: [PATCH 11/91] Update storage.js --- src/utils/data/storage.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/data/storage.js b/src/utils/data/storage.js index 15151f2c..dd8c17a4 100644 --- a/src/utils/data/storage.js +++ b/src/utils/data/storage.js @@ -165,6 +165,7 @@ const storage = { videos: hasProperty(DATA, ID.VIDEOS), presentation: hasProperty(DATA, ID.SHAPES), screenshare: hasProperty(DATA, ID.SCREENSHARE), + external_videos: hasProperty(DATA, ID.EXTERNAL_VIDEOS), }; }, get content() { @@ -176,6 +177,7 @@ const storage = { videos: !isEmpty(this.videos), presentation: hasPresentation(this.slides), screenshare: !isEmpty(this.screenshare), + external_videos: !isEmpty(this.external_videos), }; }, get alternates() { @@ -222,6 +224,9 @@ const storage = { get screenshare() { return DATA[ID.SCREENSHARE]; }, + get external_videos() { + return DATA[ID.EXTERNAL_VIDEOS]; + }, get shapes() { return DATA[ID.SHAPES]; }, From 9f0cc6ae53a617e249baebfd6c6e909fd4cb51d1 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:38:51 +0900 Subject: [PATCH 12/91] Update constants.js --- src/utils/constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/constants.js b/src/utils/constants.js index e87cbb32..fc52f601 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -41,6 +41,7 @@ const ID = { POLLS: 'polls', PRESENTATION: 'presentation', SCREENSHARE: 'screenshare', + EXTERNAL_VIDEOS: 'external_videos', SEARCH: 'search', SETTINGS: 'settings', SHAPES: 'shapes', @@ -61,6 +62,7 @@ const CONTENT = [ ID.VIDEOS, ID.NOTES, ID.SCREENSHARE, + ID.EXTERNAL_VIDEOS, ID.CAPTIONS, ]; From a61b97d83fd40443d3a233dd9ac871f78a1977e6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:40:19 +0900 Subject: [PATCH 13/91] Update proxy.js --- src/components/presentation/slide/proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/presentation/slide/proxy.js b/src/components/presentation/slide/proxy.js index 7f45c289..227a8114 100644 --- a/src/components/presentation/slide/proxy.js +++ b/src/components/presentation/slide/proxy.js @@ -17,7 +17,7 @@ const Proxy = ({ src, } = thumbnail; - if (src === ID.SCREENSHARE) return null; + if (src === ID.SCREENSHARE || src === ID.EXTERNAL_VIDEOS) return null; return ( Date: Sun, 18 Jun 2023 23:42:13 +0900 Subject: [PATCH 14/91] Update layout.js --- src/utils/layout.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/layout.js b/src/utils/layout.js index 29f75803..1afc49d5 100644 --- a/src/utils/layout.js +++ b/src/utils/layout.js @@ -71,8 +71,11 @@ const layout = { get screenshare() { return this.content.screenshare; }, + get external_videos() { + return this.content.external_videos; + }, get single() { - return !this.content.presentation && !this.content.screenshare; + return !this.content.presentation && !this.content.screenshare && !this.content.external_videos; }, hasFullscreenButton: function (content, swap) { if (!this.control || !controls.fullscreen) return false; From 864ae5716e9ebf4af6778a5ce8d7a14150cfa7e6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:44:20 +0900 Subject: [PATCH 15/91] Update synchronizer.js --- src/utils/synchronizer.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 57ff18f8..1e51aeb2 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -36,10 +36,14 @@ const EVENTS = [ ]; export default class Synchronizer { - constructor(primary, secondary) { + constructor(primary, secondary, externalVideos = null) { this.primary = primary; this.secondary = secondary; + if (externalVideos) { + this.externalVideos = externalVideos; + } + this.status = { primary: 'waiting', secondary: 'waiting', @@ -50,6 +54,24 @@ export default class Synchronizer { this.init(); } + syncVolume() { + const volume = this.primary.volume(); + const muted = this.primary.muted(); + + if (this.externalVideos) { + this.externalVideos.handleVolumeChange(volume,muted); + } + } + + handleUpdateTime() { + const currentTime = this.primary.currentTime(); + + if (this.externalVideos && this.externalVideos.time !== currentTime) + { + this.externalVideos.time = currentTime; + } + } + init() { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); @@ -69,6 +91,10 @@ export default class Synchronizer { this.secondary.playbackRate(playbackRate); }); + this.primary.on('volumechange', () => this.syncVolume()); + + this.primary.on('timeupdate', () => this.handleUpdateTime()); + this.primary.on('waiting', () => { if (!this.synching && this.status.secondary === 'canplay') { this.synching = true; From 24dcc81276234310716c62529a0a48f07e0e6d52 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:57:37 +0900 Subject: [PATCH 16/91] Update player.js --- src/utils/player.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/utils/player.js b/src/utils/player.js index 96f9372b..81b982b0 100644 --- a/src/utils/player.js +++ b/src/utils/player.js @@ -11,6 +11,9 @@ const player = { get screenshare() { return PLAYERS[ID.SCREENSHARE]; }, + get exernal_videos() { + return PLAYERS[ID.EXTERNAL_VIDEOS]; + }, get synchronizer() { return SYNCHRONIZER; }, @@ -20,8 +23,18 @@ const player = { set screenshare(value) { if (!PLAYERS[ID.SCREENSHARE]) PLAYERS[ID.SCREENSHARE] = value; - if (this.webcams) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); + if (this.external_videos.length === 0) { + if (this.webcams) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); + } + } else { + if (this.webcams) { + if (this.webcams && this.screenshare && this.external_videos) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); + } + } else { + this.synchronizer = new Synchronizer(this.screenshare, this.external_videos); + } } }, set synchronizer(value) { @@ -30,8 +43,18 @@ const player = { set webcams(value) { if (!PLAYERS[ID.WEBCAMS]) PLAYERS[ID.WEBCAMS] = value; - if (this.screenshare) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); + if (this.external_videos.length === 0) { + if (this.screenshare) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); + } + } else { + if (this.screenshare) { + if (this.webcams && this.screenshare && this.external_videos) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); + } + } else { + this.synchronizer = new Synchronizer(this.webcams, this.external_videos); + } } }, }; From be4d03117c3e4224b01ad1723abe9bd52a165675 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 19 Jun 2023 00:08:34 +0900 Subject: [PATCH 17/91] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a79d6c43..ebfa6f77 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "react-intl": "^6.2.5", "react-router-dom": "^6.4.3", "react-sizeme": "^3.0.2", + "react-player": "^2.12.0", "sass": "^1.52.1", "semver": "^7.3.8", "video.js": "^7.21.1", From 68669931f7dbeed73c0c0750f7d56f64605c9698 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 19 Jun 2023 00:11:40 +0900 Subject: [PATCH 18/91] Update en.json --- src/locales/messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/messages/en.json b/src/locales/messages/en.json index d4aa53dc..c9294f70 100644 --- a/src/locales/messages/en.json +++ b/src/locales/messages/en.json @@ -38,7 +38,7 @@ "player.search.modal.title": "Search", "player.search.modal.subtitle": "Find presentation slides content", "player.thumbnails.wrapper.aria": "Thumbnails area", - "player.webcams.wrapper.aria": "Webcams area" + "player.webcams.wrapper.aria": "Webcams area", "player.video.wrapper.aria": "Video area", "player.externalVideo.autoPlayWarning": "We need your permission for playing audio" } From fa9c0497532868982cd2436b962809a96e828a95 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 19 Jun 2023 00:17:50 +0900 Subject: [PATCH 19/91] Update index.js --- src/utils/data/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/data/index.js b/src/utils/data/index.js index 98a62dc4..07d4b9a6 100644 --- a/src/utils/data/index.js +++ b/src/utils/data/index.js @@ -72,9 +72,9 @@ const getCurrentContent = (time) => { let content=PRESENTATION; - if (isEnabled(screenshare, time)) { + if (isEnabled(storage.screenshare, time)) { content=SCREENSHARE; - } else if (isEnabled(externalVideos, time)) { + } else if (isEnabled(storage.externalVideos, time)) { content=EXTERNAL_VIDEOS; } From a4b59cc0b501b0bf5d52881c780adb47e7603f98 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 19 Jun 2023 00:18:27 +0900 Subject: [PATCH 20/91] Update storage.js --- src/utils/data/storage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/data/storage.js b/src/utils/data/storage.js index dd8c17a4..073b45c3 100644 --- a/src/utils/data/storage.js +++ b/src/utils/data/storage.js @@ -165,7 +165,7 @@ const storage = { videos: hasProperty(DATA, ID.VIDEOS), presentation: hasProperty(DATA, ID.SHAPES), screenshare: hasProperty(DATA, ID.SCREENSHARE), - external_videos: hasProperty(DATA, ID.EXTERNAL_VIDEOS), + externalVideos: hasProperty(DATA, ID.EXTERNAL_VIDEOS), }; }, get content() { @@ -177,7 +177,7 @@ const storage = { videos: !isEmpty(this.videos), presentation: hasPresentation(this.slides), screenshare: !isEmpty(this.screenshare), - external_videos: !isEmpty(this.external_videos), + externalVideos: !isEmpty(this.external_videos), }; }, get alternates() { From 377420b88b2268a39b43a007abe4dcf1a1b42cf0 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 21:32:09 +0900 Subject: [PATCH 21/91] Update index.js --- src/components/external-video-player/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 2a71efd8..fc97e92b 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -5,6 +5,7 @@ import { defineMessages } from 'react-intl'; import logger from 'utils/logger'; import { ID } from 'utils/constants'; import { getCurrentDataIndex } from 'utils/data'; +import player from 'utils/player'; import './styles.css'; @@ -96,8 +97,14 @@ class ExternalVideoPlayer extends Component { this.orchestrator = this.orchestrator.bind(this); this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); + //this.dispatchTimeUpdate = this.dispatchTimeUpdate.bind(this); } + //dispatchTimeUpdate = (time) => { + // const event = new CustomEvent(EVENTS.TIME_UPDATE, { detail: { time }}); + // document.dispatchEvent(event); + //}; + autoPlayBlockDetected() { this.setState({ autoPlayBlocked: true }); } @@ -153,9 +160,9 @@ class ExternalVideoPlayer extends Component { this.playerIsReady = true; this.handleFirstPlay(); - const { onPlayerReady } = this.props; + //const { onPlayerReady } = this.props; - if (onPlayerReady) onPlayerReady(ID.EXTERNAL_VIDEOS, this); + //if (onPlayerReady) onPlayerReady(ID.EXTERNAL_VIDEOS, this); } @@ -215,11 +222,15 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, primaryPlaybackRate } = this.props; + const { events, active, getCurrentPlayerTime, primaryPlaybackRate } = this.props; const { playing, playbackRate } = this.state; + this.time = player.primary.currentTime(); + let primaryPlayerPlaying = true; + this.handleVolumeChange(player.primary.volume(), player.primary.muted()); + if (this.time === this.lastTime) { primaryPlayerPlaying = false; } @@ -304,7 +315,7 @@ class ExternalVideoPlayer extends Component { onPause={this.handleOnPause} onBuffer={this.handleOnBuffer} onBufferEnd={this.handleOnBufferEnd} - ref={(ref) => { this.player = ref; }} + ref={(ref) => { this.player = ref; if (!player.external_videos) {player.external_videos = this.player; } }} width="100%" height="100%" /> From b0cc7ebeb4e92e7f46232027f02057010a190fb2 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:22:09 +0900 Subject: [PATCH 22/91] Update index.js --- src/components/player/content/index.js | 60 +++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 5a36fa61..7de3f57a 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -12,6 +12,16 @@ import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; +import { + //getCurrentContent, + getCurrentDataIndex, + //getCurrentDataInterval, +} from 'utils/data'; +import { useIntl } from 'react-intl'; +import player from 'utils/player'; +import { useCurrentContent } from 'components/utils/hooks'; +import { ID } from 'utils/constants'; + const Content = ({ fullscreen, handleSearch, @@ -25,6 +35,54 @@ const Content = ({ storage.panzooms.tldraw || storage.cursor.tldraw; + const RenderExternalVideo = () => { + const time = player.primary ? player.primary.currentTime() : 0; + const intl = useIntl(); + const { external_videos } = storage; + const currentContent = useCurrentContent(); + + if (!external_videos) { + return; + } + + let currentDataIndex = getCurrentDataIndex(external_videos, time); + + if (currentDataIndex === -1) { + currentDataIndex = 0; + } + + const video = external_videos[currentDataIndex]; + + if (!video) { + return + } + + const url = video.url + const events = video.events; + + let primaryPlaybackRate = 1; +/* + if (player.webcams) { + primaryPlaybackRate = player.webcams.playbackRate(); + } +*/ + // Use primary player for timing, instead of webcam player (no difference?) + if (player.primary) { + primaryPlaybackRate = player.primary.playbackRate(); + } + return ( + + ); + } + return (
{isTldrawWhiteboard ? : } {layout.screenshare ? : null} - {layout.externalVideo ? : null} + {layout.external_videos ? RenderExternalVideo() : null}
Date: Thu, 22 Jun 2023 22:22:52 +0900 Subject: [PATCH 23/91] Update index.js --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index fc97e92b..f03a67b8 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -222,7 +222,7 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, getCurrentPlayerTime, primaryPlaybackRate } = this.props; + const { events, active, /*getCurrentPlayerTime,*/ primaryPlaybackRate } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); From 00be64ed448a2a78cdf1b57ad00f7a05f1c87b82 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:30:11 +0900 Subject: [PATCH 24/91] Update builder.js --- src/utils/builder.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index fffb5549..c4f0cf40 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -498,20 +498,21 @@ const buildExternalVideos = result => { const { recording } = result; if (hasProperty(recording, 'video')) { - data = recording.video.map(video => { - const attr = getAttr(video); + const v = recording.video; + const videos = Array.isArray(v) ? v : [v]; + data = videos.map(video => { return { - timestamp: parseFloat(attr.start_timestamp), - clear: parseFloat(attr.stop_timestamp), - url: attr.url, + timestamp: parseFloat(video._start_timestamp), + clear: parseFloat(video._stop_timestamp), + url: video._url, events: video.event.map(event => { - const attr = getAttr(event); + //const attr = getAttr(event); return { - timestamp: parseFloat(attr.timestamp), - type: attr.type, - time: attr.time, - rate: parseFloat(attr.rate), - playing: (attr.playing === 'true'), + timestamp: parseFloat(event._timestamp), + type: event._type, + time: event._time, + rate: parseFloat(event._rate), + playing: (event._playing === 'true'), } }) }; From d31cad457ec177db52fd4304b79180c8a1bd10d1 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:30:50 +0900 Subject: [PATCH 25/91] Update constants.js --- src/utils/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/constants.js b/src/utils/constants.js index fc52f601..48624781 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -41,7 +41,7 @@ const ID = { POLLS: 'polls', PRESENTATION: 'presentation', SCREENSHARE: 'screenshare', - EXTERNAL_VIDEOS: 'external_videos', + EXTERNAL_VIDEOS: 'externalVideos', SEARCH: 'search', SETTINGS: 'settings', SHAPES: 'shapes', From c7db68aeb192731fb4cbbd9603a6d55a07a9370f Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:32:58 +0900 Subject: [PATCH 26/91] Update index.js --- src/utils/data/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/data/index.js b/src/utils/data/index.js index 07d4b9a6..0517d52d 100644 --- a/src/utils/data/index.js +++ b/src/utils/data/index.js @@ -74,7 +74,7 @@ const getCurrentContent = (time) => { if (isEnabled(storage.screenshare, time)) { content=SCREENSHARE; - } else if (isEnabled(storage.externalVideos, time)) { + } else if (isEnabled(storage.external_videos, time)) { content=EXTERNAL_VIDEOS; } From 0dd1bd741b0806b75acc7b289725dd8b7809bc51 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:33:55 +0900 Subject: [PATCH 27/91] Update layout.js --- src/utils/layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/layout.js b/src/utils/layout.js index 1afc49d5..3be00503 100644 --- a/src/utils/layout.js +++ b/src/utils/layout.js @@ -72,10 +72,10 @@ const layout = { return this.content.screenshare; }, get external_videos() { - return this.content.external_videos; + return this.content.externalVideos; }, get single() { - return !this.content.presentation && !this.content.screenshare && !this.content.external_videos; + return !this.content.presentation && !this.content.screenshare && !this.content.externalVideos; }, hasFullscreenButton: function (content, swap) { if (!this.control || !controls.fullscreen) return false; From 61416f51cc0ac8ddc721850a8607d1d4c5281003 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:37:40 +0900 Subject: [PATCH 28/91] Update player.js --- src/utils/player.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/utils/player.js b/src/utils/player.js index 81b982b0..2583eb10 100644 --- a/src/utils/player.js +++ b/src/utils/player.js @@ -11,7 +11,7 @@ const player = { get screenshare() { return PLAYERS[ID.SCREENSHARE]; }, - get exernal_videos() { + get external_videos() { return PLAYERS[ID.EXTERNAL_VIDEOS]; }, get synchronizer() { @@ -23,7 +23,7 @@ const player = { set screenshare(value) { if (!PLAYERS[ID.SCREENSHARE]) PLAYERS[ID.SCREENSHARE] = value; - if (this.external_videos.length === 0) { + if (!this.external_videos || this.external_videos.length === 0) { if (this.webcams) { this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } @@ -33,7 +33,24 @@ const player = { this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); } } else { - this.synchronizer = new Synchronizer(this.screenshare, this.external_videos); + this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); + } + } + }, + set external_videos(value) { + if (!PLAYERS[ID.EXTERNAL_VIDEOS]) PLAYERS[ID.EXTERNAL_VIDEOS] = value; + + if (!this.webcams || this.webcams.length === 0) { + if (this.screenshare) { + this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); + } + } else { + if (this.screenshare) { + if (this.webcams && this.screenshare && this.external_videos) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); + } + } else { + this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); } } }, @@ -43,7 +60,7 @@ const player = { set webcams(value) { if (!PLAYERS[ID.WEBCAMS]) PLAYERS[ID.WEBCAMS] = value; - if (this.external_videos.length === 0) { + if (!this.external_videos || this.external_videos?.length === 0) { if (this.screenshare) { this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } @@ -53,7 +70,7 @@ const player = { this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); } } else { - this.synchronizer = new Synchronizer(this.webcams, this.external_videos); + this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); } } }, From b20978e9415610b257d7540763c6b8716b59ace7 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:38:42 +0900 Subject: [PATCH 29/91] Update synchronizer.js --- src/utils/synchronizer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 1e51aeb2..382258f8 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -73,6 +73,7 @@ export default class Synchronizer { } init() { + if (this.secondary) { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); this.secondary.on(status, () => this.status.secondary = status); @@ -137,5 +138,6 @@ export default class Synchronizer { this.primary.on(event, () => logger.debug(`primary ${event} ${this.status.primary}`)); this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); }); + } } } From d8dac2785ce405d49f6127dabc1f84d80dc6912b Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:42:16 +0900 Subject: [PATCH 30/91] Update image.js --- src/components/thumbnails/image.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/thumbnails/image.js b/src/components/thumbnails/image.js index 3849a268..90429ee1 100644 --- a/src/components/thumbnails/image.js +++ b/src/components/thumbnails/image.js @@ -29,7 +29,7 @@ const Image = ({
); } - +/* const external_video = src === ID.EXTERNAL_VIDEOS; if (external_video) { @@ -39,7 +39,7 @@ const Image = ({ ); } - +*/ const logo = src.includes('logo'); return ( From 2f6418af2a744364d67c278c8fb7ef04a8f5c721 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:42:51 +0900 Subject: [PATCH 31/91] Update index.scss --- src/components/thumbnails/index.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/thumbnails/index.scss b/src/components/thumbnails/index.scss index 5314f422..d144241c 100644 --- a/src/components/thumbnails/index.scss +++ b/src/components/thumbnails/index.scss @@ -41,10 +41,11 @@ $thumbnail-height: calc((#{$bottom-content-height} / 10) * 6); font-size: xxx-large; font-weight: var(--font-weight-semi-bold); } - +/* .external_video { font-size: xxx-large; font-weight: var(--font-weight-semi-bold); } +*/ } } From 5fcf7d24de0348e25ad9f7d3eeb24a4a8f27d2d2 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:45:57 +0900 Subject: [PATCH 32/91] Update ja.json --- src/locales/messages/ja.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/locales/messages/ja.json b/src/locales/messages/ja.json index 926885f3..9fe4f734 100644 --- a/src/locales/messages/ja.json +++ b/src/locales/messages/ja.json @@ -38,5 +38,7 @@ "player.search.modal.title": "検索", "player.search.modal.subtitle": "プレゼンスライドの内容を探す", "player.thumbnails.wrapper.aria": "サムネイルエリア", - "player.webcams.wrapper.aria": "ウェブカムエリア" + "player.webcams.wrapper.aria": "ウェブカムエリア", + "player.video.wrapper.aria": "ビデオエリア", + "player.externalVideo.autoPlayWarning": "音声の再生には許可が必要です" } From 4224530ee7e6303df11c5369f457da6dd6e4a519 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:47:15 +0900 Subject: [PATCH 33/91] Update builder.js --- src/utils/builder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index c4f0cf40..e0f384be 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -221,12 +221,12 @@ const buildThumbnails = slides => { src: ID.SCREENSHARE, timestamp, }); - } else if (src.includes(ID.EXTERNAL_VIDEOS)) { + /*} else if (src.includes(ID.EXTERNAL_VIDEOS)) { result.push({ id, src: ID.EXTERNAL_VIDEOS, timestamp, - }); + });*/ } else { result.push({ id, From 9b1b06d7bb6007f8acf2f5ddce53104630b89ace Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:49:15 +0900 Subject: [PATCH 34/91] Update builder.js --- src/utils/builder.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index e0f384be..2cc7a6d7 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -221,12 +221,13 @@ const buildThumbnails = slides => { src: ID.SCREENSHARE, timestamp, }); - /*} else if (src.includes(ID.EXTERNAL_VIDEOS)) { + } else if (src.includes(ID.EXTERNAL_VIDEOS)) { + //Currently not implemented result.push({ id, src: ID.EXTERNAL_VIDEOS, timestamp, - });*/ + }); } else { result.push({ id, From 03a2367c11512b22d8cdd57293021f0f8ed16518 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:50:28 +0900 Subject: [PATCH 35/91] Update image.js --- src/components/thumbnails/image.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/thumbnails/image.js b/src/components/thumbnails/image.js index 90429ee1..c1cb663c 100644 --- a/src/components/thumbnails/image.js +++ b/src/components/thumbnails/image.js @@ -29,7 +29,8 @@ const Image = ({ ); } -/* + + //Currently not implemented const external_video = src === ID.EXTERNAL_VIDEOS; if (external_video) { @@ -39,7 +40,7 @@ const Image = ({ ); } -*/ + const logo = src.includes('logo'); return ( From 7b72ae1f2982cb576bc5f0ed603963d38775e837 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:53:34 +0900 Subject: [PATCH 36/91] Update player.js --- src/utils/player.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/player.js b/src/utils/player.js index 2583eb10..53949989 100644 --- a/src/utils/player.js +++ b/src/utils/player.js @@ -12,6 +12,7 @@ const player = { return PLAYERS[ID.SCREENSHARE]; }, get external_videos() { + //Not really used.. return PLAYERS[ID.EXTERNAL_VIDEOS]; }, get synchronizer() { @@ -38,6 +39,7 @@ const player = { } }, set external_videos(value) { + //Not really used.. if (!PLAYERS[ID.EXTERNAL_VIDEOS]) PLAYERS[ID.EXTERNAL_VIDEOS] = value; if (!this.webcams || this.webcams.length === 0) { From 742462cc867562bb99bc6e3fb303b5474ff4f22a Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:06:20 +0900 Subject: [PATCH 37/91] Update index.scss --- src/components/thumbnails/index.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/thumbnails/index.scss b/src/components/thumbnails/index.scss index d144241c..0bf00149 100644 --- a/src/components/thumbnails/index.scss +++ b/src/components/thumbnails/index.scss @@ -41,11 +41,11 @@ $thumbnail-height: calc((#{$bottom-content-height} / 10) * 6); font-size: xxx-large; font-weight: var(--font-weight-semi-bold); } -/* + .external_video { font-size: xxx-large; font-weight: var(--font-weight-semi-bold); } -*/ + } } From 6846c792a9313b09a68ded8648ac981c1ccc3202 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:24:47 +0900 Subject: [PATCH 38/91] Update index.scss --- src/components/utils/icon/index.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/utils/icon/index.scss b/src/components/utils/icon/index.scss index 9d292002..556d6540 100644 --- a/src/components/utils/icon/index.scss +++ b/src/components/utils/icon/index.scss @@ -85,6 +85,10 @@ content: "\e930"; } +.icon-externalVideos:before { + content: "\e95d"; +} + .icon-dark:before { content: "\e9A0"; } From 5c4a38cf651bbb8a53e36ece70ae293c2515d62f Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:27:05 +0900 Subject: [PATCH 39/91] Delete icons.woff --- src/styles/assets/icons.woff | Bin 17548 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/styles/assets/icons.woff diff --git a/src/styles/assets/icons.woff b/src/styles/assets/icons.woff deleted file mode 100644 index 4cbb70a1df784ea887e1542a83f9ab44e0dd59d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17548 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 Date: Fri, 23 Jun 2023 00:27:19 +0900 Subject: [PATCH 40/91] Add files via upload --- src/styles/assets/icons.woff | Bin 0 -> 21276 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/styles/assets/icons.woff diff --git a/src/styles/assets/icons.woff b/src/styles/assets/icons.woff new file mode 100644 index 0000000000000000000000000000000000000000..b491aaa7b4d334abb0a6243800a2e5337fc33d9d GIT binary patch literal 21276 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 H6kY%TJzsGD literal 0 HcmV?d00001 From 7d025156d23c35bd3d581d3e7b287d513ad8e7ee Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:29:03 +0900 Subject: [PATCH 41/91] Update image.js --- src/components/thumbnails/image.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/thumbnails/image.js b/src/components/thumbnails/image.js index c1cb663c..68325c60 100644 --- a/src/components/thumbnails/image.js +++ b/src/components/thumbnails/image.js @@ -30,7 +30,6 @@ const Image = ({ ); } - //Currently not implemented const external_video = src === ID.EXTERNAL_VIDEOS; if (external_video) { From f43aa0a4cad1b48a43dd23a9e16a7581e3d4cde5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Fri, 23 Jun 2023 00:29:59 +0900 Subject: [PATCH 42/91] Update builder.js --- src/utils/builder.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index 2cc7a6d7..c4f0cf40 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -222,7 +222,6 @@ const buildThumbnails = slides => { timestamp, }); } else if (src.includes(ID.EXTERNAL_VIDEOS)) { - //Currently not implemented result.push({ id, src: ID.EXTERNAL_VIDEOS, From 74bccd185536accb411ffbd528c00e776987d2f7 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:25:30 +0900 Subject: [PATCH 43/91] Youtube control on --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index f03a67b8..84ef16da 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -72,7 +72,7 @@ class ExternalVideoPlayer extends Component { autohide: 1, rel: 0, ecver: 2, - controls: 0, + controls: 1, enablejsapi: 0, showinfo: 0 }, From cca9953017ca5b92286abadd4fe1771e97502170 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:45:24 +0900 Subject: [PATCH 44/91] disable volume sync in synchronizer --- src/utils/synchronizer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 382258f8..a33168f7 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -53,7 +53,7 @@ export default class Synchronizer { this.init(); } - +/* syncVolume() { const volume = this.primary.volume(); const muted = this.primary.muted(); @@ -62,7 +62,7 @@ export default class Synchronizer { this.externalVideos.handleVolumeChange(volume,muted); } } - +*/ handleUpdateTime() { const currentTime = this.primary.currentTime(); @@ -92,7 +92,8 @@ export default class Synchronizer { this.secondary.playbackRate(playbackRate); }); - this.primary.on('volumechange', () => this.syncVolume()); + //do this at external_video-player/index.js + //this.primary.on('volumechange', () => this.syncVolume()); this.primary.on('timeupdate', () => this.handleUpdateTime()); From e81fe8b1417f8f996e58c18387b6275270580dde Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:26:44 +0900 Subject: [PATCH 45/91] [chore] reorder --- src/components/external-video-player/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 84ef16da..48d465c4 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -226,11 +226,10 @@ class ExternalVideoPlayer extends Component { const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); - - let primaryPlayerPlaying = true; this.handleVolumeChange(player.primary.volume(), player.primary.muted()); + let primaryPlayerPlaying = true; if (this.time === this.lastTime) { primaryPlayerPlaying = false; } From 978f9f855ca80f37ad9247cec0c16bed27605bd5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:48:11 +0900 Subject: [PATCH 46/91] better synchronisation 1. remove volume sync and rate sync from here. (-> external-video-player/index.js) 2. more precisely distribute 'if (this.secondary)' into the code --- src/utils/synchronizer.js | 59 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index a33168f7..c4f9b8ad 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -66,33 +66,41 @@ export default class Synchronizer { handleUpdateTime() { const currentTime = this.primary.currentTime(); - if (this.externalVideos && this.externalVideos.time !== currentTime) - { + if (this.externalVideos && this.externalVideos.time !== currentTime) { + // only this one works, but volume, muted, rate are not tractable from here by this way.. this.externalVideos.time = currentTime; } } init() { - if (this.secondary) { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); - this.secondary.on(status, () => this.status.secondary = status); + if (this.secondary) { + this.secondary.on(status, () => this.status.secondary = status); + } }); this.primary.on('play', () => this.secondary.play()); - this.primary.on('pause', () => this.secondary.pause()); + if (this.secondary) { + this.primary.on('pause', () => this.secondary.pause()); + } this.primary.on('seeking', () => { const currentTime = this.primary.currentTime(); - this.secondary.currentTime(currentTime); + if (this.secondary) { + this.secondary.currentTime(currentTime); + } }); this.primary.on('ratechange', () => { const playbackRate = this.primary.playbackRate(); - this.secondary.playbackRate(playbackRate); + if (this.secondary) { + this.secondary.playbackRate(playbackRate); + } }); //do this at external_video-player/index.js + // (not by passing props but by getting the values from primary player directly (dirty..) //this.primary.on('volumechange', () => this.syncVolume()); this.primary.on('timeupdate', () => this.handleUpdateTime()); @@ -111,19 +119,21 @@ export default class Synchronizer { } }); - this.secondary.on('waiting', () => { - if (!this.synching && this.status.primary === 'canplay') { - this.synching = true; - this.primary.pause(); - } - }); - - this.secondary.on('canplay', () => { - if (this.synching) { - this.synching = false; - this.primary.play(); - } - }); + if (this.secondary) { + this.secondary.on('waiting', () => { + if (!this.synching && this.status.primary === 'canplay') { + this.synching = true; + this.primary.pause(); + } + }); + + this.secondary.on('canplay', () => { + if (this.synching) { + this.synching = false; + this.primary.play(); + } + }); + } // IMPORTANT: Blink holds the secondary media down while the document // page is not visible @@ -131,14 +141,17 @@ export default class Synchronizer { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { const currentTime = this.primary.currentTime(); - this.secondary.currentTime(currentTime); + if (this.secondary) { + this.secondary.currentTime(currentTime); + } } }); EVENTS.forEach(event => { this.primary.on(event, () => logger.debug(`primary ${event} ${this.status.primary}`)); - this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); + if (this.secondary) { + this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); + } }); - } } } From b76535d7a138bf3a57dc3e00b5ac75f69c36d7f9 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:53:53 +0900 Subject: [PATCH 47/91] better synchronisation Remove primary player's value from props that will be passed to the external video player (because it did not work by whatever reasons...) --- src/components/player/content/index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 7de3f57a..3f2293a3 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -60,16 +60,23 @@ const Content = ({ const url = video.url const events = video.events; - let primaryPlaybackRate = 1; + //let primaryPlaybackRate = 1; + //let primaryPlaybackVolume = 1; + //let primaryPlaybackMuted = false; /* if (player.webcams) { primaryPlaybackRate = player.webcams.playbackRate(); } */ // Use primary player for timing, instead of webcam player (no difference?) + // -> in the end this way to pass props did not work... +/* if (player.primary) { primaryPlaybackRate = player.primary.playbackRate(); + primaryPlaybackVolume = player.primary.volume(); + primaryPlaybackMuted = player.primary.muted(); } +*/ return ( ); From 6d23564319fc3ff66462032010433a8812beb4e4 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:00:34 +0900 Subject: [PATCH 48/91] better synchronisation obtain the rate, muted, volume directly from the primary player and pass them to this.state. The ordinary way to pass them as props did not work....? --- src/components/external-video-player/index.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 48d465c4..0e03ef6e 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -131,16 +131,16 @@ class ExternalVideoPlayer extends Component { } - setPlaybackRate() { - - const { primaryPlaybackRate } = this.props; + setPlaybackRate(value) { + //The original way to get the rate from props did not work...? + //const { primaryPlaybackRate } = this.props; // Rate depends on primary rate player - const rate = primaryPlaybackRate * this.lastEventPlaybackRate; + const rate = value * this.lastEventPlaybackRate; const currentRate = this.state.playbackRate; - logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${primaryPlaybackRate} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); if (currentRate === rate) { return; @@ -222,18 +222,19 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { events, active, /*getCurrentPlayerTime,*/ primaryPlaybackRate } = this.props; + const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/ } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); - this.handleVolumeChange(player.primary.volume(), player.primary.muted()); - let primaryPlayerPlaying = true; if (this.time === this.lastTime) { primaryPlayerPlaying = false; } + //this.handleVolumeChange(primaryPlaybackVolume, primaryPlaybackMuted); // did not work...? + this.handleVolumeChange(player.primary.volume(), player.primary.muted()); + this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; @@ -278,7 +279,8 @@ class ExternalVideoPlayer extends Component { } } - this.setPlaybackRate(); + //this.setPlaybackRate(); + this.setPlaybackRate(player.primary.playbackRate()); } From 686c5ef80d5c6980c0165abdc78e574767b5cd70 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:52:45 +0900 Subject: [PATCH 49/91] better synchronisation comment why passing volume, mute, and rate by props does not work. --- src/components/player/content/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 3f2293a3..b23b3494 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -69,7 +69,8 @@ const Content = ({ } */ // Use primary player for timing, instead of webcam player (no difference?) - // -> in the end this way to pass props did not work... + // -> in the end this way to pass props did not work, + // because player/content would be rendered only once in the beginning. /* if (player.primary) { primaryPlaybackRate = player.primary.playbackRate(); From ebf1beb59f880a6a2fd5dae4d74386048a385009 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 24 Jun 2023 13:54:24 +0900 Subject: [PATCH 50/91] Update index.js --- src/components/external-video-player/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 0e03ef6e..585e35ba 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -132,7 +132,8 @@ class ExternalVideoPlayer extends Component { setPlaybackRate(value) { - //The original way to get the rate from props did not work...? + //The original way to get the rate from props did not work, + // because props will not be updated after the initial rendering. //const { primaryPlaybackRate } = this.props; // Rate depends on primary rate player From 3a6d99c5b6dd367c9f64c9960b6f8e4dca92e21c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:36:34 +0900 Subject: [PATCH 51/91] [bug] second player --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index c4f9b8ad..098feb1f 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -80,8 +80,8 @@ export default class Synchronizer { } }); - this.primary.on('play', () => this.secondary.play()); if (this.secondary) { + this.primary.on('play', () => this.secondary.play()); this.primary.on('pause', () => this.secondary.pause()); } From 249f41451d6faf8a8bad99b7cfcc7ef1c3391646 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:06:55 +0900 Subject: [PATCH 52/91] [fix] hitchhiking on thumbnails Since the externalvideo component does not get rerendered in the current implementation, I move all rerendering process into external-video-player.js --- src/components/player/content/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index b23b3494..c71a4b40 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -57,8 +57,14 @@ const Content = ({ return } - const url = video.url const events = video.events; + + const videos = external_videos.map(video => { + return { + url: video.url, + time: [video.timestamp, video.clear], + } + }); //let primaryPlaybackRate = 1; //let primaryPlaybackVolume = 1; @@ -82,7 +88,7 @@ const Content = ({ Date: Wed, 28 Jun 2023 22:15:57 +0900 Subject: [PATCH 53/91] [fix] hitchhiking on thumbnails Look for the video url for each cycle of orchestrator... --- src/components/external-video-player/index.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 585e35ba..881b29ad 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -49,6 +49,7 @@ class ExternalVideoPlayer extends Component { errorPlaying: false, playbackRate: 1, volume: 1, + urlPlayed: "", }; this.opts = { @@ -97,6 +98,7 @@ class ExternalVideoPlayer extends Component { this.orchestrator = this.orchestrator.bind(this); this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); + this.whichVideo = this.whichVideo.bind(this); //this.dispatchTimeUpdate = this.dispatchTimeUpdate.bind(this); } @@ -222,8 +224,13 @@ class ExternalVideoPlayer extends Component { clearInterval(this.timer); } + whichVideo = (videos, time) => { + const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); + return found ? found.url : ""; + } + orchestrator () { - const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/ } = this.props; + const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); @@ -243,10 +250,14 @@ class ExternalVideoPlayer extends Component { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } - const index = getCurrentDataIndex(events, this.time); + if (active) { + const currentVideo = this.whichVideo(videos, this.time); + this.setState({ urlPlayed: currentVideo }); + } - logger.debug(`external_video: player time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + const index = getCurrentDataIndex(events, this.time); + logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); if (!primaryPlayerPlaying || !active) { this.handleOnPause(); @@ -287,8 +298,8 @@ class ExternalVideoPlayer extends Component { render() { - const { videoUrl, active, intl } = this.props; - const { playing, playbackRate, muted, autoPlayBlocked, volume } = this.state; + const { /*videoUrl,*/ active, intl } = this.props; + const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; return ( @@ -306,7 +317,7 @@ class ExternalVideoPlayer extends Component { } Date: Wed, 28 Jun 2023 23:10:48 +0900 Subject: [PATCH 54/91] [fix] hitchhiking on thumbnails Maybe not necessary.. --- src/components/external-video-player/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 881b29ad..203d4b6a 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -250,13 +250,13 @@ class ExternalVideoPlayer extends Component { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } + const index = getCurrentDataIndex(events, this.time); + if (active) { const currentVideo = this.whichVideo(videos, this.time); this.setState({ urlPlayed: currentVideo }); } - const index = getCurrentDataIndex(events, this.time); - logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); if (!primaryPlayerPlaying || !active) { @@ -301,6 +301,11 @@ class ExternalVideoPlayer extends Component { const { /*videoUrl,*/ active, intl } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; + if (urlPlayed == "") { + const currentVideo = this.whichVideo(videos, this.time); + this.setState({ urlPlayed: currentVideo }); + } + return (
Date: Thu, 29 Jun 2023 00:09:29 +0900 Subject: [PATCH 55/91] handleUpdateTime does not really work now.. --- src/utils/synchronizer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 098feb1f..a80f20fc 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -103,7 +103,8 @@ export default class Synchronizer { // (not by passing props but by getting the values from primary player directly (dirty..) //this.primary.on('volumechange', () => this.syncVolume()); - this.primary.on('timeupdate', () => this.handleUpdateTime()); + // Actually this does not work at all.. + //this.primary.on('timeupdate', () => this.handleUpdateTime()); this.primary.on('waiting', () => { if (!this.synching && this.status.secondary === 'canplay') { From fc82edfe0495cb48a8a35a359d91c0038e9bfeeb Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 29 Jun 2023 00:18:15 +0900 Subject: [PATCH 56/91] refactor external-video-player Thumbnail hitchhiking started to work, but after jumping on a external video, you cannot use the primary player's slider to seek the video. But when the player enters external video from a normal slide, the slider works well. Obviously the synchronise function stops to work if the react-player is not re-rendered..need to find where the external video is synchronised with the primary player. --- src/components/external-video-player/index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 203d4b6a..46a5b27a 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -226,7 +226,8 @@ class ExternalVideoPlayer extends Component { whichVideo = (videos, time) => { const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); - return found ? found.url : ""; + //return found ? found.url : ""; + return found ? found : {url: "", time:[0, 0]}; } orchestrator () { @@ -254,7 +255,11 @@ class ExternalVideoPlayer extends Component { if (active) { const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo }); + if (currentVideo.url !== this.state.urlPlayed) { + this.setState({ urlPlayed: currentVideo.url }); + } + // This skrews up the playback (playing back and forth..), but should work in theory + //this.seekTo(this.time - currentVideo.time[0]); } logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); @@ -298,12 +303,12 @@ class ExternalVideoPlayer extends Component { render() { - const { /*videoUrl,*/ active, intl } = this.props; + const { /*videoUrl,*/ active, intl, video } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; if (urlPlayed == "") { const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo }); + this.setState({ urlPlayed: currentVideo.url }); } return ( From 73a312da595c0548ccd288a0b9d075ce4743b64d Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:05:03 +0900 Subject: [PATCH 57/91] more simple implementation --- src/components/external-video-player/index.js | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 46a5b27a..e4ca4ced 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -19,7 +19,7 @@ const intlMessages = defineMessages({ }); -const SYNC_INTERVAL_SECOND = 5; +const SYNC_INTERVAL_SECOND = 1; const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; const ORCHESTRATOR_INTERVAL_MILLISECOND = 500; @@ -128,7 +128,7 @@ class ExternalVideoPlayer extends Component { getCurrentTime() { if (this.player && this.player.getCurrentTime) { - return Math.round(this.player.getCurrentTime()); + return this.player.getCurrentTime(); } } @@ -143,12 +143,11 @@ class ExternalVideoPlayer extends Component { const currentRate = this.state.playbackRate; - logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); - if (currentRate === rate) { return; } + logger.debug(`external_video: setPlaybackRate current=${currentRate} primary=${value} lastEventPlaybackRate=${this.lastEventPlaybackRate} rate=${rate}`); this.setState({ playbackRate: rate }); } @@ -197,8 +196,14 @@ class ExternalVideoPlayer extends Component { } handleVolumeChange = (value, isMuted) => { - this.setState({ volume: parseFloat(value)}); - this.setState({ muted: isMuted}); + if (this.state.volume !== value ) { + this.setState({ volume: parseFloat(value)}); + logger.debug(`external_video: VolumeChange CV=${this.state.volume.toFixed(2)} NV=${value.toFixed(2)}`); + } + if (this.state.muted != isMuted) { + this.setState({ muted: isMuted}); + logger.debug(`external_video: muteChange CM=${this.state.muted} NM=${isMuted}`); + } } @@ -212,6 +217,7 @@ class ExternalVideoPlayer extends Component { // Seek if viewer has drifted too far away from presenter if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + logger.debug(`Video synchronised! ${(time - this.getCurrentTime()).toFixed(2)} `); player.seekTo(time, true); } } @@ -225,13 +231,12 @@ class ExternalVideoPlayer extends Component { } whichVideo = (videos, time) => { - const found = videos.find(video => video.time[0] <= time && video.time[1] >= time); - //return found ? found.url : ""; - return found ? found : {url: "", time:[0, 0]}; + const found = videos.find(video => video.timestamp <= time && video.clear >= time); + return found ? found : {url: "", timestamp: 0, clear: 0}; } orchestrator () { - const { events, active/*, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; + const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); @@ -247,32 +252,32 @@ class ExternalVideoPlayer extends Component { this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; - if (active && !this.hasPlayedBefore && !this.autoPlayTimeout) { + if (/*active &&*/ !this.hasPlayedBefore && !this.autoPlayTimeout) { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } + const currentVideo = this.whichVideo(videos, this.time); const index = getCurrentDataIndex(events, this.time); - if (active) { + //if (active) { const currentVideo = this.whichVideo(videos, this.time); if (currentVideo.url !== this.state.urlPlayed) { this.setState({ urlPlayed: currentVideo.url }); + logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); } - // This skrews up the playback (playing back and forth..), but should work in theory - //this.seekTo(this.time - currentVideo.time[0]); - } + //} - logger.debug(`external_video: player url=${currentVideo} time=${this.time} active=${active} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}`); + logger.debug(`external_video: player url=${this.state.urlPlayed} time=${this.time.toFixed(2)} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}, events=${currentVideo.events}`); - if (!primaryPlayerPlaying || !active) { + if (!primaryPlayerPlaying /*|| !active*/) { this.handleOnPause(); this.playerUpdateTime = -1; return } - if (index && events && events[index] && events[index].type) + if (index && currentVideo.events && currentVideo.events[index] && currentVideo.events[index].type) { - const {type, time, rate, playing} = events[index]; + const {type, time, rate, playing} = currentVideo.events[index]; logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); @@ -303,18 +308,14 @@ class ExternalVideoPlayer extends Component { render() { - const { /*videoUrl,*/ active, intl, video } = this.props; + const { /*videoUrl, active,*/ intl/*, video*/ } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; - if (urlPlayed == "") { - const currentVideo = this.whichVideo(videos, this.time); - this.setState({ urlPlayed: currentVideo.url }); - } - + logger.debug(`Rendered ${urlPlayed}. Note this shouldn't be shown frequently!`); return (
{ this.playerParent = ref; }} > {autoPlayBlocked @@ -338,7 +339,7 @@ class ExternalVideoPlayer extends Component { onPause={this.handleOnPause} onBuffer={this.handleOnBuffer} onBufferEnd={this.handleOnBufferEnd} - ref={(ref) => { this.player = ref; if (!player.external_videos) {player.external_videos = this.player; } }} + ref={(ref) => { this.player = ref; }} width="100%" height="100%" /> From a62ad9b9f2bfec1a3d0f4b13beb3fbfafe88c4e7 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:13:52 +0900 Subject: [PATCH 58/91] more simple implementation --- src/components/external-video-player/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e4ca4ced..d54e4220 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -260,11 +260,16 @@ class ExternalVideoPlayer extends Component { const index = getCurrentDataIndex(events, this.time); //if (active) { - const currentVideo = this.whichVideo(videos, this.time); if (currentVideo.url !== this.state.urlPlayed) { this.setState({ urlPlayed: currentVideo.url }); logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); } + // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much + if (index && currentVideo.events && currentVideo.events[index] && playing && (currentVideo.events[index].type == "playerUpdate" || currentVideo.events[index].type == "play") ){ + const thisPlayerTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp); + logger.debug(`Player time to be=${thisPlayerTimeToBe.toFixed(2)} actual player time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisPlayerTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); + this.seekTo(thisPlayerTimeToBe); + } //} logger.debug(`external_video: player url=${this.state.urlPlayed} time=${this.time.toFixed(2)} Playing=${playing} primaryPlayerPlaying=${primaryPlayerPlaying} PlaybackRate=${playbackRate}, events=${currentVideo.events}`); From 8a831859660c0480e271a4ba034b1a971c6d4604 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:16:00 +0900 Subject: [PATCH 59/91] Update index.js --- src/components/external-video-player/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index d54e4220..e3d72382 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -316,9 +316,8 @@ class ExternalVideoPlayer extends Component { const { /*videoUrl, active,*/ intl/*, video*/ } = this.props; const { playing, playbackRate, muted, autoPlayBlocked, volume, urlPlayed } = this.state; - logger.debug(`Rendered ${urlPlayed}. Note this shouldn't be shown frequently!`); + logger.debug(`Rendering ${urlPlayed}. Note this shouldn't be shown frequently!`); return ( -
{ this.playerParent = ref; }} From eb2e5cb451c88574eb9f6357f2465c3cc3ba918c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:19:14 +0900 Subject: [PATCH 60/91] more simple implementation --- src/components/player/content/index.js | 51 ++------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index c71a4b40..1e15f4d0 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -36,61 +36,16 @@ const Content = ({ storage.cursor.tldraw; const RenderExternalVideo = () => { - const time = player.primary ? player.primary.currentTime() : 0; const intl = useIntl(); const { external_videos } = storage; - const currentContent = useCurrentContent(); - - if (!external_videos) { - return; - } - let currentDataIndex = getCurrentDataIndex(external_videos, time); - - if (currentDataIndex === -1) { - currentDataIndex = 0; - } - - const video = external_videos[currentDataIndex]; - - if (!video) { - return - } - - const events = video.events; - - const videos = external_videos.map(video => { - return { - url: video.url, - time: [video.timestamp, video.clear], - } - }); - - //let primaryPlaybackRate = 1; - //let primaryPlaybackVolume = 1; - //let primaryPlaybackMuted = false; -/* - if (player.webcams) { - primaryPlaybackRate = player.webcams.playbackRate(); - } -*/ - // Use primary player for timing, instead of webcam player (no difference?) - // -> in the end this way to pass props did not work, - // because player/content would be rendered only once in the beginning. -/* - if (player.primary) { - primaryPlaybackRate = player.primary.playbackRate(); - primaryPlaybackVolume = player.primary.volume(); - primaryPlaybackMuted = player.primary.muted(); - } -*/ return ( Date: Sat, 1 Jul 2023 09:23:51 +0900 Subject: [PATCH 61/91] more simple implementation reverting the changes --- src/utils/player.js | 46 ++++----------------------------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/src/utils/player.js b/src/utils/player.js index 53949989..59372452 100644 --- a/src/utils/player.js +++ b/src/utils/player.js @@ -24,36 +24,8 @@ const player = { set screenshare(value) { if (!PLAYERS[ID.SCREENSHARE]) PLAYERS[ID.SCREENSHARE] = value; - if (!this.external_videos || this.external_videos.length === 0) { - if (this.webcams) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); - } - } else { - if (this.webcams) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); - } - } - }, - set external_videos(value) { - //Not really used.. - if (!PLAYERS[ID.EXTERNAL_VIDEOS]) PLAYERS[ID.EXTERNAL_VIDEOS] = value; - - if (!this.webcams || this.webcams.length === 0) { - if (this.screenshare) { - this.synchronizer = new Synchronizer(null, this.screenshare, this.external_videos); - } - } else { - if (this.screenshare) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); - } + if (this.webcams) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } }, set synchronizer(value) { @@ -62,18 +34,8 @@ const player = { set webcams(value) { if (!PLAYERS[ID.WEBCAMS]) PLAYERS[ID.WEBCAMS] = value; - if (!this.external_videos || this.external_videos?.length === 0) { - if (this.screenshare) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare); - } - } else { - if (this.screenshare) { - if (this.webcams && this.screenshare && this.external_videos) { - this.synchronizer = new Synchronizer(this.webcams, this.screenshare, this.external_videos); - } - } else { - this.synchronizer = new Synchronizer(this.webcams, null, this.external_videos); - } + if (this.screenshare) { + this.synchronizer = new Synchronizer(this.webcams, this.screenshare); } }, }; From e503a6de9ca52cc2536399a18829984767f278ef Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:28:24 +0900 Subject: [PATCH 62/91] more simple implementation revert the changes --- src/utils/synchronizer.js | 69 ++++++++++----------------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index a80f20fc..8c717af6 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -53,59 +53,26 @@ export default class Synchronizer { this.init(); } -/* - syncVolume() { - const volume = this.primary.volume(); - const muted = this.primary.muted(); - - if (this.externalVideos) { - this.externalVideos.handleVolumeChange(volume,muted); - } - } -*/ - handleUpdateTime() { - const currentTime = this.primary.currentTime(); - - if (this.externalVideos && this.externalVideos.time !== currentTime) { - // only this one works, but volume, muted, rate are not tractable from here by this way.. - this.externalVideos.time = currentTime; - } - } init() { STATUSES.forEach(status => { this.primary.on(status, () => this.status.primary = status); - if (this.secondary) { - this.secondary.on(status, () => this.status.secondary = status); - } + this.secondary.on(status, () => this.status.secondary = status); }); - if (this.secondary) { - this.primary.on('play', () => this.secondary.play()); - this.primary.on('pause', () => this.secondary.pause()); - } + this.primary.on('play', () => this.secondary.play()); + this.primary.on('pause', () => this.secondary.pause()); this.primary.on('seeking', () => { const currentTime = this.primary.currentTime(); - if (this.secondary) { - this.secondary.currentTime(currentTime); - } + this.secondary.currentTime(currentTime); }); this.primary.on('ratechange', () => { const playbackRate = this.primary.playbackRate(); - if (this.secondary) { - this.secondary.playbackRate(playbackRate); - } + this.secondary.playbackRate(playbackRate); }); - //do this at external_video-player/index.js - // (not by passing props but by getting the values from primary player directly (dirty..) - //this.primary.on('volumechange', () => this.syncVolume()); - - // Actually this does not work at all.. - //this.primary.on('timeupdate', () => this.handleUpdateTime()); - this.primary.on('waiting', () => { if (!this.synching && this.status.secondary === 'canplay') { this.synching = true; @@ -120,21 +87,19 @@ export default class Synchronizer { } }); - if (this.secondary) { - this.secondary.on('waiting', () => { - if (!this.synching && this.status.primary === 'canplay') { - this.synching = true; - this.primary.pause(); - } - }); + jjjjthis.secondary.on('waiting', () => { + if (!this.synching && this.status.primary === 'canplay') { + this.synching = true; + this.primary.pause(); + } + }); - this.secondary.on('canplay', () => { - if (this.synching) { - this.synching = false; - this.primary.play(); - } - }); - } + this.secondary.on('canplay', () => { + if (this.synching) { + this.synching = false; + this.primary.play(); + } + }); // IMPORTANT: Blink holds the secondary media down while the document // page is not visible From 791701182a58686781944ee0c1713247868ce1cb Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:29:26 +0900 Subject: [PATCH 63/91] Update synchronizer.js --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 8c717af6..16882e43 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -87,7 +87,7 @@ export default class Synchronizer { } }); - jjjjthis.secondary.on('waiting', () => { + this.secondary.on('waiting', () => { if (!this.synching && this.status.primary === 'canplay') { this.synching = true; this.primary.pause(); From ee9fcb6cf9dcae9f69d16d25bd0bac559b3c2ac9 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:32:26 +0900 Subject: [PATCH 64/91] Update index.js --- src/components/player/content/index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 1e15f4d0..765ff24c 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -12,16 +12,6 @@ import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; -import { - //getCurrentContent, - getCurrentDataIndex, - //getCurrentDataInterval, -} from 'utils/data'; -import { useIntl } from 'react-intl'; -import player from 'utils/player'; -import { useCurrentContent } from 'components/utils/hooks'; -import { ID } from 'utils/constants'; - const Content = ({ fullscreen, handleSearch, From ac5b8ceda35918d4766e6d89bce74a9c35c15cf6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:33:42 +0900 Subject: [PATCH 65/91] Update index.js --- src/components/external-video-player/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e3d72382..f7c89911 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -9,6 +9,7 @@ import player from 'utils/player'; import './styles.css'; +import { useIntl } from 'react-intl'; const intlMessages = defineMessages({ autoPlayWarning: { From 5bdbf15e317f12abf1ea21405c53703bfe612751 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:34:07 +0900 Subject: [PATCH 66/91] Update index.js --- src/components/external-video-player/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index f7c89911..7b77321c 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -9,8 +9,6 @@ import player from 'utils/player'; import './styles.css'; -import { useIntl } from 'react-intl'; - const intlMessages = defineMessages({ autoPlayWarning: { id: 'player.externalVideo.autoPlayWarning', From 3f4f70ab234912e819c1e4382c66923e0eef0bf6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:34:45 +0900 Subject: [PATCH 67/91] Update index.js --- src/components/player/content/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index 765ff24c..b6f2d04c 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -12,6 +12,8 @@ import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; +import { useIntl } from 'react-intl'; + const Content = ({ fullscreen, handleSearch, From 0d781b5e3f7a11f9d5f055fc0dec67c5ed2a4fd5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:35:42 +0900 Subject: [PATCH 68/91] Update index.js --- src/components/player/content/index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/player/content/index.js b/src/components/player/content/index.js index b6f2d04c..998595da 100644 --- a/src/components/player/content/index.js +++ b/src/components/player/content/index.js @@ -11,8 +11,19 @@ import { isEqual } from 'utils/data/validators'; import layout from 'utils/layout'; import storage from 'utils/data/storage'; import './index.scss'; - +/* +import { + //getCurrentContent, + getCurrentDataIndex, + //getCurrentDataInterval, +} from 'utils/data'; +*/ import { useIntl } from 'react-intl'; +/* +import player from 'utils/player'; +import { useCurrentContent } from 'components/utils/hooks'; +import { ID } from 'utils/constants'; +*/ const Content = ({ fullscreen, From d9d713a2540cc54668bc759295cae7b88d899991 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:39:23 +0900 Subject: [PATCH 69/91] more simple implementation --- src/utils/synchronizer.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 16882e43..2dc8f5b5 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -40,9 +40,9 @@ export default class Synchronizer { this.primary = primary; this.secondary = secondary; - if (externalVideos) { - this.externalVideos = externalVideos; - } + //if (externalVideos) { + // this.externalVideos = externalVideos; + //} this.status = { primary: 'waiting', @@ -107,17 +107,13 @@ export default class Synchronizer { document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { const currentTime = this.primary.currentTime(); - if (this.secondary) { - this.secondary.currentTime(currentTime); - } + this.secondary.currentTime(currentTime); } }); EVENTS.forEach(event => { this.primary.on(event, () => logger.debug(`primary ${event} ${this.status.primary}`)); - if (this.secondary) { - this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); - } + this.secondary.on(event, () => logger.debug(`secondary ${event} ${this.status.secondary}`)); }); } } From 63b82afe5b8374b1dfb1815061cf96fcb9554c79 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:43:19 +0900 Subject: [PATCH 70/91] Update synchronizer.js --- src/utils/synchronizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/synchronizer.js b/src/utils/synchronizer.js index 2dc8f5b5..04c185fe 100644 --- a/src/utils/synchronizer.js +++ b/src/utils/synchronizer.js @@ -36,7 +36,7 @@ const EVENTS = [ ]; export default class Synchronizer { - constructor(primary, secondary, externalVideos = null) { + constructor(primary, secondary/*, externalVideos = null*/) { this.primary = primary; this.secondary = secondary; From e1f98a81794179e1effd926f0bbed1dd8eb6c25c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 09:58:05 +0900 Subject: [PATCH 71/91] suppress warning of autoplay block --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 7b77321c..706f27cd 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -251,7 +251,7 @@ class ExternalVideoPlayer extends Component { this.lastTime = this.time; this.primaryPlayerPlaying = primaryPlayerPlaying; - if (/*active &&*/ !this.hasPlayedBefore && !this.autoPlayTimeout) { + if (/*active &&*/playing && !this.hasPlayedBefore && !this.autoPlayTimeout) { this.autoPlayTimeout = setTimeout(this.autoPlayBlockDetected, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000); } From 4e23e34b59a25f76b4ee31217a525beeeadd9055 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:03:45 +0900 Subject: [PATCH 72/91] Update index.js --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 706f27cd..9e0c945c 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -235,7 +235,7 @@ class ExternalVideoPlayer extends Component { } orchestrator () { - const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted*/, videos } = this.props; + const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted,*/ videos } = this.props; const { playing, playbackRate } = this.state; this.time = player.primary.currentTime(); From 09e129f1cfd85b1c584f0a44799cc19e69e0aa94 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:07:33 +0900 Subject: [PATCH 73/91] Update index.js --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 9e0c945c..e6343e72 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -256,7 +256,7 @@ class ExternalVideoPlayer extends Component { } const currentVideo = this.whichVideo(videos, this.time); - const index = getCurrentDataIndex(events, this.time); + const index = getCurrentDataIndex(currentVideo.events, this.time); //if (active) { if (currentVideo.url !== this.state.urlPlayed) { From a53df84003b43ff76a93e022d1b5dc175ef60fb3 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 1 Jul 2023 10:33:14 +0900 Subject: [PATCH 74/91] Remove 0.75, allowing 1s inconsistency because starting the video usually delays ~1 sec due to loading the data. Prevents an initial quick reload happening every time when a video starts. --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e6343e72..fc6e5f29 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -215,7 +215,7 @@ class ExternalVideoPlayer extends Component { } // Seek if viewer has drifted too far away from presenter - if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND * 0.75) { + if (Math.abs(this.getCurrentTime() - time) > SYNC_INTERVAL_SECOND) { logger.debug(`Video synchronised! ${(time - this.getCurrentTime()).toFixed(2)} `); player.seekTo(time, true); } From 56fbf1c026f7b276efed1f5ef2461cf7d63ffd91 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:20:19 +0900 Subject: [PATCH 75/91] Add files via upload --- src/styles/assets/icons.woff | Bin 21276 -> 17840 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/styles/assets/icons.woff b/src/styles/assets/icons.woff index b491aaa7b4d334abb0a6243800a2e5337fc33d9d..5ace36bc90afb5f168c7784ee31eba11ee93d276 100644 GIT binary patch literal 17840 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 Bo;Cmg literal 21276 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 H6kY%TJzsGD From fc3c8b6f72e97a6749489db04df56e1bfd7920c5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:24:05 +0900 Subject: [PATCH 76/91] Fix: sync at diff rate Fix a bug where video unnecessarily synchronised with higher/lower play rate --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index fc6e5f29..b106ab2f 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -265,7 +265,7 @@ class ExternalVideoPlayer extends Component { } // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much if (index && currentVideo.events && currentVideo.events[index] && playing && (currentVideo.events[index].type == "playerUpdate" || currentVideo.events[index].type == "play") ){ - const thisPlayerTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp); + const thisPlayerTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp)*currentVideo.events[index].rate; logger.debug(`Player time to be=${thisPlayerTimeToBe.toFixed(2)} actual player time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisPlayerTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); this.seekTo(thisPlayerTimeToBe); } From 6e71656bae00b56e94edaa801bdb99de1273a46c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:48:24 +0900 Subject: [PATCH 77/91] Refactor orchestration --- src/components/external-video-player/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index b106ab2f..029e7d72 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -265,9 +265,12 @@ class ExternalVideoPlayer extends Component { } // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much if (index && currentVideo.events && currentVideo.events[index] && playing && (currentVideo.events[index].type == "playerUpdate" || currentVideo.events[index].type == "play") ){ - const thisPlayerTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp)*currentVideo.events[index].rate; - logger.debug(`Player time to be=${thisPlayerTimeToBe.toFixed(2)} actual player time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisPlayerTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); - this.seekTo(thisPlayerTimeToBe); + // thisMovieTimeToBe = MovieTimeToBe + (currentPlayerTime - eventTimeStamp) * playRate + // [movie time after calibration] [from the start of the movie] [how much sec from the timestamp of a update event] + const thisMovieTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp) * currentVideo.events[index].rate; + logger.debug(`eventTimeStamp=${currentVideo.events[index].timestamp.toFixed(2)} MovieTimeToBe=${parseFloat(currentVideo.events[index].time).toFixed(2)} currentPlayerTime=${this.time.toFixed(2)} currentMovieTime=${this.player.getCurrentTime().toFixed(2)} rate=${currentVideo.events[index].rate}`); + logger.debug(`Calibrated movie time to be=${thisMovieTimeToBe.toFixed(2)} actual movie time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisMovieTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); + this.seekTo(thisMovieTimeToBe); } //} From 60133c31729c2daca0a6189421138282d2b6471d Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:34:53 +0900 Subject: [PATCH 78/91] bump react-player to 2.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db9a7012..f96a4842 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "react-intl": "^6.2.5", "react-router-dom": "^6.4.3", "react-sizeme": "^3.0.2", - "react-player": "^2.12.0", + "react-player": "^2.16.0", "sass": "^1.52.1", "semver": "^7.5.2", "tldraw-v1": "npm:@tldraw/tldraw@^1.2.7", From f2de2f3fd10b48f8ec2f499cc2bf768b413a0d4f Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:41:13 +0900 Subject: [PATCH 79/91] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E5=8B=95=E3=81=8F=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最終的にはループのダサい感じを解消すべき。 --- src/components/external-video-player/index.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 029e7d72..af080bf5 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -20,7 +20,8 @@ const intlMessages = defineMessages({ const SYNC_INTERVAL_SECOND = 1; const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; -const ORCHESTRATOR_INTERVAL_MILLISECOND = 500; +const ORCHESTRATOR_INTERVAL_MILLISECOND = 200; +const IGNORE_STOP_CLOSE_TO_START_SECOND = 0.5; class ExternalVideoPlayer extends Component { @@ -165,6 +166,7 @@ class ExternalVideoPlayer extends Component { //if (onPlayerReady) onPlayerReady(ID.EXTERNAL_VIDEOS, this); + this.handleOnPlay(); } @@ -287,7 +289,13 @@ class ExternalVideoPlayer extends Component { const {type, time, rate, playing} = currentVideo.events[index]; logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); - + + const nextEvent = currentVideo.events[index+1]; + if (nextEvent && type == "stop" && nextEvent.type == "play" && (nextEvent.time - time) < IGNORE_STOP_CLOSE_TO_START_SECOND ){ + logger.debug(`external_video: player skipped "stop" event due to a close "play", interval=${nextEvent.time - time}`); + return; + } + switch (type) { case "stop": this.handleOnPause(); @@ -295,7 +303,7 @@ class ExternalVideoPlayer extends Component { case "play": this.handleOnPlay(); break; - case "playerUpdate": + case "playerUpdate": case "setPlaybackRate": case "seek": if (this.playerUpdateTime !== time) { this.lastEventPlaybackRate=rate; this.seekTo(time); @@ -309,7 +317,7 @@ class ExternalVideoPlayer extends Component { } //this.setPlaybackRate(); - this.setPlaybackRate(player.primary.playbackRate()); + this.setPlaybackRate(player.primary.playbackRate()); // Is it necessary?? } From 8741b4f43ce8cec623ea85a85bb9a4cb231f7a20 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:59:45 +0900 Subject: [PATCH 80/91] Fix: error when no events in exteral_videos.xml SInce 3.0 external video events are much more simplified than before. Sometimes there is only one tag with start and stop information, without any event tag at the inside. --- src/utils/builder.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils/builder.js b/src/utils/builder.js index 0021b1cf..6bc9c7d0 100644 --- a/src/utils/builder.js +++ b/src/utils/builder.js @@ -528,20 +528,20 @@ const buildExternalVideos = result => { const v = recording.video; const videos = Array.isArray(v) ? v : [v]; data = videos.map(video => { - return { - timestamp: parseFloat(video._start_timestamp), - clear: parseFloat(video._stop_timestamp), - url: video._url, - events: video.event.map(event => { - //const attr = getAttr(event); - return { + const events = Array.isArray(video.event) + ? video.event.map(event => ({ timestamp: parseFloat(event._timestamp), type: event._type, time: event._time, rate: parseFloat(event._rate), playing: (event._playing === 'true'), - } - }) + })) + : []; + return { + timestamp: parseFloat(video._start_timestamp), + clear: parseFloat(video._stop_timestamp), + url: video._url, + events, }; }); } From 46e49689182f318a4856e21a578eed59f571df01 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 4 Oct 2025 13:53:56 +0900 Subject: [PATCH 81/91] Optimization Enlarge the inverval. replace Component with PureComponent, which massively reduces render() call. --- src/components/external-video-player/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index af080bf5..75d35ffc 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import ReactPlayer from 'react-player'; import cx from 'classnames'; import { defineMessages } from 'react-intl'; @@ -20,10 +20,10 @@ const intlMessages = defineMessages({ const SYNC_INTERVAL_SECOND = 1; const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; -const ORCHESTRATOR_INTERVAL_MILLISECOND = 200; +const ORCHESTRATOR_INTERVAL_MILLISECOND = 300; const IGNORE_STOP_CLOSE_TO_START_SECOND = 0.5; -class ExternalVideoPlayer extends Component { +class ExternalVideoPlayer extends PureComponent { constructor(props) { super(props); From 0dfe5628b0edc7dab88b77bdf939dfb9649ec89f Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 4 Oct 2025 21:42:25 +0900 Subject: [PATCH 82/91] Fix: Rate change works now --- src/components/external-video-player/index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 75d35ffc..6bca2289 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -259,6 +259,10 @@ class ExternalVideoPlayer extends PureComponent { const currentVideo = this.whichVideo(videos, this.time); const index = getCurrentDataIndex(currentVideo.events, this.time); + if (index < 0) { + // when the primary player rewinds before the start of external video + this.lastEventPlaybackRate = 1; + } //if (active) { if (currentVideo.url !== this.state.urlPlayed) { @@ -303,9 +307,9 @@ class ExternalVideoPlayer extends PureComponent { case "play": this.handleOnPlay(); break; - case "playerUpdate": case "setPlaybackRate": case "seek": + case "playerUpdate": case "setPlaybackRate": // befor v3 + case "seek": case "playbackRateChange": // from v3 if (this.playerUpdateTime !== time) { - this.lastEventPlaybackRate=rate; this.seekTo(time); playing ? this.handleOnPlay() : this.handleOnPause() this.playerUpdateTime=time; @@ -314,10 +318,11 @@ class ExternalVideoPlayer extends PureComponent { default: ; } + // Play rate being adjusted every time. + this.lastEventPlaybackRate=rate; } - - //this.setPlaybackRate(); - this.setPlaybackRate(player.primary.playbackRate()); // Is it necessary?? + // multiply the primary player's playing rate + this.setPlaybackRate(player.primary.playbackRate()); } From d27820a28228876cbab8c388ee6acb1a3bf63c04 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:46:13 +0900 Subject: [PATCH 83/91] fix rate -> 1 also when the video play finishes --- src/components/external-video-player/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 6bca2289..6e087a0a 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -259,8 +259,8 @@ class ExternalVideoPlayer extends PureComponent { const currentVideo = this.whichVideo(videos, this.time); const index = getCurrentDataIndex(currentVideo.events, this.time); - if (index < 0) { - // when the primary player rewinds before the start of external video + if (index < 0 || currentVideo.clear < this.time) { + // when the primary player rewinds before the start of external video or finishes the video play. this.lastEventPlaybackRate = 1; } From 5d6f42dfb6edbd7eca27e8a5d53704f5438fd3ea Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 4 Oct 2025 23:02:00 +0900 Subject: [PATCH 84/91] Fix: play time consistency Delete unnecessary conditions. Previously the player always plays from the start of the video when the user rewinds the player after the video play finishes, and the last event is i.e., "playbackRateChange". --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 6e087a0a..0106c488 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -270,7 +270,7 @@ class ExternalVideoPlayer extends PureComponent { logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); } // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much - if (index && currentVideo.events && currentVideo.events[index] && playing && (currentVideo.events[index].type == "playerUpdate" || currentVideo.events[index].type == "play") ){ + if (index && currentVideo.events && currentVideo.events[index] && playing){ // thisMovieTimeToBe = MovieTimeToBe + (currentPlayerTime - eventTimeStamp) * playRate // [movie time after calibration] [from the start of the movie] [how much sec from the timestamp of a update event] const thisMovieTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp) * currentVideo.events[index].rate; From 5b2b2f5418966a2dcc9d827b903a77d383edcbea Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sat, 4 Oct 2025 23:48:07 +0900 Subject: [PATCH 85/91] Fix: enable seek for a one stroke video This fixes a problem that did not exist ${this.state.urlPlayed}`); } // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much - if (index && currentVideo.events && currentVideo.events[index] && playing){ - // thisMovieTimeToBe = MovieTimeToBe + (currentPlayerTime - eventTimeStamp) * playRate - // [movie time after calibration] [from the start of the movie] [how much sec from the timestamp of a update event] - const thisMovieTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp) * currentVideo.events[index].rate; - logger.debug(`eventTimeStamp=${currentVideo.events[index].timestamp.toFixed(2)} MovieTimeToBe=${parseFloat(currentVideo.events[index].time).toFixed(2)} currentPlayerTime=${this.time.toFixed(2)} currentMovieTime=${this.player.getCurrentTime().toFixed(2)} rate=${currentVideo.events[index].rate}`); - logger.debug(`Calibrated movie time to be=${thisMovieTimeToBe.toFixed(2)} actual movie time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisMovieTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); + if (playing) { + let thisMovieTimeToBe; + if (index && currentVideo.events && currentVideo.events[index]) { + // thisMovieTimeToBe = MovieTimeToBe + (currentPlayerTime - eventTimeStamp) * playRate + // [movie time after calibration] [from the start of the movie] [how much sec from the timestamp of a update event] + thisMovieTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp) * currentVideo.events[index].rate; + logger.debug(`eventTimeStamp=${currentVideo.events[index].timestamp.toFixed(2)} MovieTimeToBe=${parseFloat(currentVideo.events[index].time).toFixed(2)} currentPlayerTime=${this.time.toFixed(2)} currentMovieTime=${this.player.getCurrentTime().toFixed(2)} rate=${currentVideo.events[index].rate}`); + logger.debug(`Calibrated movie time to be=${thisMovieTimeToBe.toFixed(2)} actual movie time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisMovieTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); + } else if (currentVideo.events.length == 0) { + // Just a single video without pause, rate change, or whatever other events + thisMovieTimeToBe = this.time - currentVideo.timestamp; + } this.seekTo(thisMovieTimeToBe); } //} From 262a773192493c87af7753d744282d7bd2aaab07 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 5 Oct 2025 00:01:53 +0900 Subject: [PATCH 86/91] Remove unnecessary import --- src/components/external-video-player/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 3065471d..adbfa2d3 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -3,7 +3,7 @@ import ReactPlayer from 'react-player'; import cx from 'classnames'; import { defineMessages } from 'react-intl'; import logger from 'utils/logger'; -import { ID } from 'utils/constants'; +//import { ID } from 'utils/constants'; import { getCurrentDataIndex } from 'utils/data'; import player from 'utils/player'; From 0312de9e9556dae8d9a841be950488ef3852126f Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 5 Oct 2025 08:31:57 +0900 Subject: [PATCH 87/91] Refactor getCurrentTime --- src/components/external-video-player/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index adbfa2d3..45ba83c6 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -127,12 +127,10 @@ class ExternalVideoPlayer extends PureComponent { } getCurrentTime() { - if (this.player && this.player.getCurrentTime) { - return this.player.getCurrentTime(); - } + const time = this.player?.getCurrentTime?.(); + return typeof time === 'number' ? time : 0; } - setPlaybackRate(value) { //The original way to get the rate from props did not work, // because props will not be updated after the initial rendering. From 1ee04f92ff6a845dd526ec912f8837097ee49ae2 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:26:26 +0900 Subject: [PATCH 88/91] Fix some hidden bugs - if (index) is false when index is 0. - '==' is replaced with '===' according to the npm compiler - player.primary can be undefined (but probably not anymore). Just a fail safe. --- src/components/external-video-player/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 45ba83c6..7c151b06 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -172,8 +172,8 @@ class ExternalVideoPlayer extends PureComponent { const { playing } = this.state; if (!playing && this.primaryPlayerPlaying) { - this.setState({ playing: true }); - this.handleFirstPlay(); + this.setState({ playing: true }); + this.handleFirstPlay(); } } @@ -199,7 +199,7 @@ class ExternalVideoPlayer extends PureComponent { this.setState({ volume: parseFloat(value)}); logger.debug(`external_video: VolumeChange CV=${this.state.volume.toFixed(2)} NV=${value.toFixed(2)}`); } - if (this.state.muted != isMuted) { + if (this.state.muted !== isMuted) { this.setState({ muted: isMuted}); logger.debug(`external_video: muteChange CM=${this.state.muted} NM=${isMuted}`); } @@ -238,6 +238,8 @@ class ExternalVideoPlayer extends PureComponent { const { /*events, active, primaryPlaybackRate, primaryPlaybackVolume, primaryPlaybackMuted,*/ videos } = this.props; const { playing, playbackRate } = this.state; + if (!player.primary) return; + this.time = player.primary.currentTime(); let primaryPlayerPlaying = true; @@ -270,13 +272,13 @@ class ExternalVideoPlayer extends PureComponent { // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much if (playing) { let thisMovieTimeToBe; - if (index && currentVideo.events && currentVideo.events[index]) { + if (index > -1 && currentVideo.events && currentVideo.events[index]) { // thisMovieTimeToBe = MovieTimeToBe + (currentPlayerTime - eventTimeStamp) * playRate // [movie time after calibration] [from the start of the movie] [how much sec from the timestamp of a update event] thisMovieTimeToBe = parseFloat(currentVideo.events[index].time) + (this.time - currentVideo.events[index].timestamp) * currentVideo.events[index].rate; logger.debug(`eventTimeStamp=${currentVideo.events[index].timestamp.toFixed(2)} MovieTimeToBe=${parseFloat(currentVideo.events[index].time).toFixed(2)} currentPlayerTime=${this.time.toFixed(2)} currentMovieTime=${this.player.getCurrentTime().toFixed(2)} rate=${currentVideo.events[index].rate}`); logger.debug(`Calibrated movie time to be=${thisMovieTimeToBe.toFixed(2)} actual movie time=${this.player.getCurrentTime().toFixed(2)} inconsistency=${(thisMovieTimeToBe - this.player.getCurrentTime()).toFixed(2)} Type=${currentVideo.events[index].type}`); - } else if (currentVideo.events.length == 0) { + } else if (currentVideo.events && currentVideo.events.length === 0) { // Just a single video without pause, rate change, or whatever other events thisMovieTimeToBe = this.time - currentVideo.timestamp; } @@ -292,14 +294,14 @@ class ExternalVideoPlayer extends PureComponent { return } - if (index && currentVideo.events && currentVideo.events[index] && currentVideo.events[index].type) + if (index > -1 && currentVideo.events && currentVideo.events[index] && currentVideo.events[index].type) { const {type, time, rate, playing} = currentVideo.events[index]; logger.debug(`External Video Event: type=${type} time=${time} rate=${rate} playing=${playing}`); const nextEvent = currentVideo.events[index+1]; - if (nextEvent && type == "stop" && nextEvent.type == "play" && (nextEvent.time - time) < IGNORE_STOP_CLOSE_TO_START_SECOND ){ + if (nextEvent && type === "stop" && nextEvent.type === "play" && (nextEvent.time - time) < IGNORE_STOP_CLOSE_TO_START_SECOND ){ logger.debug(`external_video: player skipped "stop" event due to a close "play", interval=${nextEvent.time - time}`); return; } From 94e5b652262523f1db9d2018c3544fb20e63f0e5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:31:42 +0900 Subject: [PATCH 89/91] requestAnimationFrame introduced instead of setInterval --- src/components/external-video-player/index.js | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 7c151b06..5e67ac38 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -20,7 +20,7 @@ const intlMessages = defineMessages({ const SYNC_INTERVAL_SECOND = 1; const AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS = 5; -const ORCHESTRATOR_INTERVAL_MILLISECOND = 300; +//const ORCHESTRATOR_INTERVAL_MILLISECOND = 300; const IGNORE_STOP_CLOSE_TO_START_SECOND = 0.5; class ExternalVideoPlayer extends PureComponent { @@ -41,6 +41,8 @@ class ExternalVideoPlayer extends PureComponent { this.playerUpdateTime = -1; this.primaryPlayerPlaying = false; this.lastEventPlaybackRate = 1; + this.isOrchestrating = false; + this.rafId = null; this.state = { muted: false, @@ -96,6 +98,9 @@ class ExternalVideoPlayer extends PureComponent { this.handleOnBufferEnd = this.handleOnBufferEnd.bind(this); this.orchestrator = this.orchestrator.bind(this); + this.startOrchestrator = this.startOrchestrator.bind(this); + this.stopOrchestrator = this.stopOrchestrator.bind(this); + this.autoPlayBlockDetected = this.autoPlayBlockDetected.bind(this); this.whichVideo = this.whichVideo.bind(this); @@ -222,13 +227,34 @@ class ExternalVideoPlayer extends PureComponent { } componentDidMount () { - this.timer = setInterval(() => this.orchestrator(), ORCHESTRATOR_INTERVAL_MILLISECOND); + //this.timer = setInterval(() => this.orchestrator(), ORCHESTRATOR_INTERVAL_MILLISECOND); + this.startOrchestrator(); } componentWillUnmount () { - clearInterval(this.timer); + //clearInterval(this.timer); + this.stopOrchestrator(); } + startOrchestrator() { + //logger.debug("startOrchestrator"); + if (this.isOrchestrating) return; + this.isOrchestrating = true; + const loop = () => { + if (!this.isOrchestrating) return; + this.orchestrator(); + this.rafId = requestAnimationFrame(loop); + }; + this.rafId = requestAnimationFrame(loop); + } + + stopOrchestrator() { + //logger.debug("stopOrchestrator"); + this.isOrchestrating = false; + if (this.rafId) cancelAnimationFrame(this.rafId); + this.rafId = null; + } + whichVideo = (videos, time) => { const found = videos.find(video => video.timestamp <= time && video.clear >= time); return found ? found : {url: "", timestamp: 0, clear: 0}; From 46bfbf11432538131acb1e7eb9667d6f27c2de3b Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:40:02 +0900 Subject: [PATCH 90/91] Fix: play video without events (one stroke video) index === -1 means either the timestamp has already passed this video, or there is no events (events is an empty array). Here, it always means the latter. So this can catch the case where the user rewinds a one-stroke video before it ends. --- src/components/external-video-player/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index 5e67ac38..e6764ff8 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -352,6 +352,10 @@ class ExternalVideoPlayer extends PureComponent { } // Play rate being adjusted every time. this.lastEventPlaybackRate=rate; + } else if (index === -1 && (currentVideo.events && currentVideo.events.length === 0)) { + // start video without events, with the playing rate 1 (supposed to be) + this.lastEventPlaybackRate = 1; + this.handleOnPlay(); } // multiply the primary player's playing rate this.setPlaybackRate(player.primary.playbackRate()); From 7b4972e8b7c78096c1e033a5d93d475151bcc861 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:48:47 +0900 Subject: [PATCH 91/91] state.playing -> false when swapping videos Does not change so much... --- src/components/external-video-player/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/external-video-player/index.js b/src/components/external-video-player/index.js index e6764ff8..c55a3844 100644 --- a/src/components/external-video-player/index.js +++ b/src/components/external-video-player/index.js @@ -294,6 +294,7 @@ class ExternalVideoPlayer extends PureComponent { if (currentVideo.url !== this.state.urlPlayed) { this.setState({ urlPlayed: currentVideo.url }); logger.debug(`external_video URLchange ${currentVideo.url} -> ${this.state.urlPlayed}`); + this.setState({ playing: false }); // When swapping to a different video. } // Check time consistency every ORCHESTRATOR_INTERVAL_MILLISECOND msec, and fix when drifted away too much if (playing) {