summaryrefslogtreecommitdiff
path: root/animism-align/frontend/app/views
diff options
context:
space:
mode:
Diffstat (limited to 'animism-align/frontend/app/views')
-rw-r--r--animism-align/frontend/app/views/align/components/player/playButton.component.js21
-rw-r--r--animism-align/frontend/app/views/nav/header.component.js3
-rw-r--r--animism-align/frontend/app/views/viewer/modals/modals.video.js188
-rw-r--r--animism-align/frontend/app/views/viewer/nav/nav.css4
-rw-r--r--animism-align/frontend/app/views/viewer/player/components.inline/inline.video.js22
-rw-r--r--animism-align/frontend/app/views/viewer/player/player.transcript.css36
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 {