diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-12-02 15:00:48 +0100 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-12-02 15:00:48 +0100 |
| commit | fa287a8b341926fa3866d7dee31b4f4090a5c98f (patch) | |
| tree | 62a72177acaf569a444e4d06a09ed8b705647920 /animism-align/frontend/app/views | |
| parent | 441b9efe7a76e801d5d079a70dcb4aeb1a132bb9 (diff) | |
starting full video modal
Diffstat (limited to 'animism-align/frontend/app/views')
6 files changed, 255 insertions, 19 deletions
diff --git a/animism-align/frontend/app/views/align/components/player/playButton.component.js b/animism-align/frontend/app/views/align/components/player/playButton.component.js index 03603ce..f411941 100644 --- a/animism-align/frontend/app/views/align/components/player/playButton.component.js +++ b/animism-align/frontend/app/views/align/components/player/playButton.component.js @@ -1,21 +1,22 @@ -import React, { Component } from 'react' -import { connect } from 'react-redux' +import React from 'react' import actions from 'app/actions' -const PlayButton = ({ audio }) => { +const PlayButton = ({ playing, onClick }) => { return ( <div - className={audio.playing ? 'playButton playing' : 'playButton paused'} + className={playing ? 'playButton playing' : 'playButton paused'} onClick={() => { - audio.playing ? actions.audio.pause() : actions.audio.play() + if (onClick) { + onClick(playing) + } else if (playing) { + actions.audio.pause() + } else { + actions.audio.play() + } }} /> ) } -const mapStateToProps = state => ({ - audio: state.audio, -}) - -export default connect(mapStateToProps)(PlayButton) +export default PlayButton diff --git a/animism-align/frontend/app/views/nav/header.component.js b/animism-align/frontend/app/views/nav/header.component.js index a4b9665..89dd60c 100644 --- a/animism-align/frontend/app/views/nav/header.component.js +++ b/animism-align/frontend/app/views/nav/header.component.js @@ -13,7 +13,7 @@ function Header(props) { } return ( <header> - <PlayButton /> + <PlayButton playing={this.props.playing} /> <div> <Link to="/align">Timeline</Link> <Link to="/paragraph">Transcript</Link> @@ -38,6 +38,7 @@ const mapStateToProps = (state) => ({ // auth: state.auth, site: state.site, router: state.router, + playing: state.audio.playing, // username: session.get('username'), // isAuthenticated: state.auth.isAuthenticated, }) diff --git a/animism-align/frontend/app/views/viewer/modals/modals.video.js b/animism-align/frontend/app/views/viewer/modals/modals.video.js new file mode 100644 index 0000000..e94b4c4 --- /dev/null +++ b/animism-align/frontend/app/views/viewer/modals/modals.video.js @@ -0,0 +1,188 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import VimeoPlayer from 'app/utils/vendor/vimeo' + +import actions from 'app/actions' +import { timestampToSeconds } from 'app/utils' +import { VideoScrubber, VideoSubtitles } from '../components.media' + +class ModalVideo extends Component { + state = { + // duration of the video, in seconds + duration: 60.0, + // percentage offset from vimeo. not used + percent: 0.0, + // current timestamp from vimeo, in seconds + seconds: 0.0, + // video start offset, in seconds + video_start_ts: 0.0, + // seek position, used to tell the player to seek + seek: 0.0, + // whether or not the scrubber is scrubbing + scrubbing: false, + // whether or not the video is ready and displaying an image + ready: false, + // trigger an audio fade-out + fadeOut: false, + // volume + volume: 1.0, + volumeFadeTime: 1000, + lastCue: -1, + } + constructor(props) { + super(props) + this.handlePlay = this.handlePlay.bind(this) + this.handlePause = this.handlePause.bind(this) + this.handleTimeUpdate = this.handleTimeUpdate.bind(this) + this.handleScrub = this.handleScrub.bind(this) + this.handleEnd = this.handleEnd.bind(this) + } + componentDidMount() { + const video_start_ts = timestampToSeconds(this.props.element.settings.video_start_ts) || 0.0 + // TODO: here you can add the current play time modulo ... + this.setState({ + seek: video_start_ts, + video_start_ts, + }) + } + componentDidUpdate(prevProps) { + // if the play_ts jumped more than a second, seek + const { play_ts, element, currentSection, fadeOutDuration } = this.props + if (Math.abs(play_ts - prevProps.play_ts) > 1.0) { + // handle seek + const { duration, video_start_ts } = this.state + const seek = ((play_ts - element.start_ts) % duration) + video_start_ts + this.setState({ seek }) + } + // console.log(play_ts, currentSection.cues.length, fadeOutDuration, element.settings.unmuted, this.state.fadeOut) + // volume changes - only if unmuted and not already leaving. + if (element.settings.unmuted && !this.state.fadeOut) { + // if we just started leaving the element, fade out the audio + if (fadeOutDuration) { + // console.log("fade out audio", this.props.fadeOutDuration) + setTimeout(() => this.setState({ fadeOut: true }), 0) + } + // otherwise, check if we gotta set the volume based on the position + else if (currentSection.cues.length) { + this.checkCuePosition(play_ts, currentSection.cues) + } + } + } + checkCuePosition(play_ts, cues) { + // const { volume element.settings.unmuted ? volume : 0.0 + const { unmuted } = this.props.element.settings + let volume = unmuted ? 1.0 : 0.0 + let { volumeFadeTime, lastCue } = this.state + // console.log(cues) + cues.some((cue, i) => { + if (cue.start_ts < play_ts) { + lastCue = i + if (cue.settings.volume) { + volume = parseFloat(cue.settings.volume) + volumeFadeTime = timestampToSeconds(cue.settings.duration) * 1000 + // console.log(volume, volumeFadeTime) + return false + } + } + return true + }) + // console.log('last cue', lastCue, volume, volumeFadeTime) + if (lastCue !== this.state.lastCue) { + this.setState({ volume, volumeFadeTime, lastCue }) + } + } + handlePlay() { + this.setState({ ready: true }) + } + handlePause() { + + } + handleEnd() { + + } + handleTimeUpdate(timing) { + if (!this.state.scrubbing || ('scrubbing' in timing)) { + this.setState(timing) + } + } + handleScrub(timing) { + // console.log(timing) + this.setState(timing) + } + + render() { + const { element, media, transitionDuration, play_ts, playing, cc, playerVolume } = this.props + const { duration, seconds, ready, volume, volumeFadeTime, fadeOut, lastCue } = this.state + const { color } = element + const item = media.lookup[element.settings.media_id] + const style = { + backgroundColor: color.backgroundColor, + color: color.textColor, + transitionDuration, + } + const playerStyle = { + opacity: ready ? 1.0 : 0.0 + } + // + // console.log(volume, playerVolume) + return ( + <div + className='fullscreen-element video' + style={style} + > + <div + className='videoPlayer' + style={playerStyle} + > + <VimeoPlayer + video={item.url} + paused={!playing} + autoplay={true} + autopause={false} + muted={!element.settings.unmuted} + loop={!!element.settings.loop} + seek={this.state.seek} + responsive={true} + controls={false} + byline={false} + volume={element.settings.unmuted ? playerVolume : 0.0} + targetVolume={element.settings.unmuted ? volume * playerVolume : 0.0} + volumeFadeTime={volumeFadeTime} + fadeOut={fadeOut && this.props.fadeOutDuration} + onPlay={this.handlePlay} + onPause={this.handlePause} + onTimeUpdate={this.handleTimeUpdate} + onEnd={this.handleEnd} + lastCue={lastCue} + /> + </div> + <VideoScrubber + play_ts={play_ts} + start_ts={element.start_ts} + video_start_ts={this.state.video_start_ts} + playing={playing} + duration={element.duration} + timing={this.state} + mediaItem={item} + cc={cc} + onScrub={this.handleScrub} + /> + <VideoSubtitles + cc={cc} + play_ts={seconds} + mediaItem={item} + /> + </div> + ) + } +} + +const mapStateToProps = state => ({ + viewer: state.viewer, + play_ts: state.audio.play_ts, + playing: state.audio.playing, + playerVolume: state.audio.volume, + cc: state.audio.cc, +}) + +export default connect(mapStateToProps)(ModalVideo) diff --git a/animism-align/frontend/app/views/viewer/nav/nav.css b/animism-align/frontend/app/views/viewer/nav/nav.css index b993750..bb49539 100644 --- a/animism-align/frontend/app/views/viewer/nav/nav.css +++ b/animism-align/frontend/app/views/viewer/nav/nav.css @@ -116,6 +116,8 @@ .next-link { padding: 0.25rem 0.25rem 0.25rem 0.25rem; user-select: none; + transition: transform 0.2s; + transform: translateZ(0); } .transcript-link { padding-right: 1.25rem; @@ -150,7 +152,7 @@ transition: transform 0.2s; transform: translateZ(0); } -.viewer-nav.hovering-next .nav-next { +.viewer-nav.hovering-next .next-link { transform: translateZ(0) translateY(-8.5rem); } .viewer-nav.hovering-next .next-section-thumbnail { diff --git a/animism-align/frontend/app/views/viewer/player/components.inline/inline.video.js b/animism-align/frontend/app/views/viewer/player/components.inline/inline.video.js index 9a0ff7a..ea73d03 100644 --- a/animism-align/frontend/app/views/viewer/player/components.inline/inline.video.js +++ b/animism-align/frontend/app/views/viewer/player/components.inline/inline.video.js @@ -50,9 +50,9 @@ export class MediaVideo extends Component { color: color.textColor, backgroundColor: color.backgroundColor, } - style.color = color.textColor, - style.backgroundColor = color.backgroundColor, - style.backgroundImage = 'url(' + posterURL(item) + ')' + style.color = color.textColor + style.backgroundColor = color.backgroundColor + // style.backgroundImage = 'url(' + posterURL(item) + ')' if (annotation.settings.poster_size) { style.backgroundSize = annotation.settings.poster_size } @@ -63,7 +63,21 @@ export class MediaVideo extends Component { style={style} onClick={e => onAnnotationClick(e, paragraph, annotation)} > - <div className="speaker-icon">{SpeakerIcon}</div> + <div className='posterImage'> + <img src={posterURL(item)} /> + {annotation.settings.can_play_full_video ? ( + <div className="speaker-icon speaker-msg"> + <div> + {SpeakerIcon} + <span>Watch full film</span> + </div> + </div> + ) : ( + <div className="speaker-icon"> + {SpeakerIcon} + </div> + )} + </div> </div> <MediaCitation media={item} /> </div> diff --git a/animism-align/frontend/app/views/viewer/player/player.transcript.css b/animism-align/frontend/app/views/viewer/player/player.transcript.css index caac222..0a333bb 100644 --- a/animism-align/frontend/app/views/viewer/player/player.transcript.css +++ b/animism-align/frontend/app/views/viewer/player/player.transcript.css @@ -166,6 +166,8 @@ cursor: default; } +/* speaker icon stuff */ + .player-transcript .paragraph { position: relative; } @@ -183,6 +185,9 @@ .audio-paused.section-end .not-scrollable .speaker-icon { transform: translateZ(0) translateY(-3rem); } +.audio-paused.section-end .not-scrollable .posterImage .speaker-icon { + transform: translateZ(0); +} .paragraph .speaker-icon { top: -1.5rem; left: -5.5rem; @@ -212,6 +217,20 @@ .player-transcript.black .speaker-icon svg path { fill: black; } +.speaker-icon.speaker-msg { + opacity: 1; +} +.speaker-msg div { + display: flex; + justify-content: center; + align-items: center; + background: white; + color: black; + border-radius: 1.5rem; +} +.speaker-msg div span { + padding: 0 1rem 0 0; +} /* media */ @@ -269,12 +288,23 @@ .player-transcript .media.video .videoPoster { position: relative; cursor: pointer; - background-size: cover; - background-position: center center; - background-repeat: no-repeat; + /*background-size: cover;*/ + /*background-position: center center;*/ + /*background-repeat: no-repeat;*/ width: 100%; height: calc(100vh - 17rem) !important; padding: 1rem 0; + text-align: center; +} +.player-transcript .media.video .videoPoster .posterImage { + display: inline-block; + position: relative; + height: 100%; +} +.player-transcript .media.video .videoPoster .posterImage img { + height: 100%; + display: block; + pointer-events: none; } .player-transcript .media.video .videoContainer { |
