-
Notifications
You must be signed in to change notification settings - Fork 1
Add detail player to film detail page #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8074b8f
a54aa95
a31a52a
0aea26e
c6f3b84
ba7419d
dce8e6e
794d7e9
f8ac4be
ebe7fd8
415eb75
383d859
f5d2e8e
5031697
41cbac0
da0bbe1
73690db
2a6f7a2
8686878
b8ccc04
8658aa0
e99c506
6acd384
f7726e5
3bca5c4
01ab080
a120abd
2c10b9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { Component } from 's72.ui'; | ||
|
|
||
| export class DetailPlayerIframe extends Component { | ||
| constructor(props, context) { | ||
| super(props, context); | ||
| } | ||
|
|
||
| render(props) { | ||
| const {slug, onLoad} = props; | ||
| let startTime = new URLSearchParams(window.location.search).get('t'); | ||
| let t = startTime ? startTime : 0; | ||
| return ( | ||
| <iframe | ||
| id="detail-player" | ||
| src={`/play/#${slug}?tm=true&t=${t}&from=`} | ||
| width="100%" | ||
| height="100%" | ||
| frameborder={0} | ||
| allowFullScreen | ||
| onLoad={onLoad} | ||
| ></iframe> | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| import { i18n } from 's72'; | ||
| import { AppComponent, classes } from 's72.ui'; | ||
| import { LockOpenIcon, LockClosedIcon, PlayIcon } from './icons'; | ||
|
|
||
| export class DetailPlayerPlaceholder extends AppComponent { | ||
|
|
||
| constructor(props, context) { | ||
| super(props, context); | ||
| this.state = { | ||
| bgLoaded: !!!props.bgImage, | ||
| titleLoaded: !!!props.titleImage, | ||
| availabilityLoaded: false, | ||
| canBeWatched: false, | ||
| wigglin: false, | ||
| playClicked: false, | ||
| purchaseCompleted: false, | ||
| unlockBegin: false, | ||
| unlockAnim: 0, | ||
| } | ||
| } | ||
|
|
||
| async componentDidMount() { | ||
| this.app.messagebus.subscribe('shopping-session-completed', async () => { | ||
| if (await this.canBeWatched()) { | ||
| this.setState({ purchaseCompleted: true }); | ||
| } | ||
| }); | ||
| window.addEventListener('message', e => { | ||
| if (e.data.event == 's72-checkout:close' && this.state.purchaseCompleted) { | ||
| this.playUnlockAnimation(); | ||
| } | ||
| }); | ||
| this.setState({ availabilityLoaded: true, canBeWatched: await this.canBeWatched() }); | ||
| } | ||
|
|
||
| componentWillUnmount() { | ||
| this.app.messagebus.unsubscribe('shopping-session-completed'); | ||
| } | ||
|
|
||
| canBeWatched() { | ||
| return this.app.availabilityService.getAvailability(this.props.slug) | ||
| .then(a => a?.canBeWatched ?? false) | ||
| .catch(() => false); | ||
| } | ||
|
|
||
| render(props, state) { | ||
| const {playerLoaded} = props; | ||
| const showPlaceholder = (state.availabilityLoaded && state.bgLoaded && state.titleLoaded) && !playerLoaded; | ||
| return ( | ||
| <div | ||
| class={classes('detail-player-placeholder', {'detail-player-placeholder--visible': showPlaceholder})} | ||
| onClick={() => this.playWiggleAnimation()} | ||
| > | ||
| {this.placeholderBG()} | ||
| <div class="detail-player-placeholder-overlay"> | ||
| {state.canBeWatched && this.playButton() } | ||
| {!state.canBeWatched && this.lockedMessage()} | ||
| <div class="detail-player-placeholder-gradient" /> | ||
| {this.titleLogo()} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| placeholderBG() { | ||
| return ( | ||
| <ImageLoader | ||
| src={this.props.bgImage} | ||
| state={this.state.bgLoaded} | ||
| onLoad={() => this.setState({ bgLoaded: true })} | ||
| > | ||
| <div | ||
| class="detail-player-placeholder-bg" | ||
| style={`background-image: url(${this.props.bgImage})`} | ||
| /> | ||
| </ImageLoader> | ||
| ); | ||
| } | ||
|
|
||
| titleLogo() { | ||
| return ( | ||
| <ImageLoader | ||
| src={this.props.titleImage} | ||
| state={this.state.titleLoaded} | ||
| onLoad={() => this.setState({ titleLoaded: true })} | ||
| > | ||
| <img | ||
| class="detail-player-placeholder-title" | ||
| src={this.props.titleImage} | ||
| /> | ||
| </ImageLoader> | ||
| ); | ||
| } | ||
|
|
||
| playWiggleAnimation() { | ||
| if (!this.state.wigglin && !this.state.unlockBegin) { | ||
| this.setState({ wigglin: true }); | ||
| setTimeout(() => { | ||
| this.setState({ wigglin: false }); | ||
| }, 900); | ||
| } | ||
| } | ||
|
|
||
| lockedMessage() { | ||
| return ( | ||
| <div class={classes('detail-player-locked-message', {'detail-player-locked-message--unlocked': this.state.unlockBegin})}> | ||
| <div class={classes('detail-player-lock-icon-wrapper', { | ||
| 'detail-player-lock-icon-wrapper--wiggle': this.state.wigglin, | ||
| 'detail-player-lock-icon-wrapper--large': this.state.unlockBegin | ||
| })}> | ||
| <div class={`detail-player-lock-icon detail-player-lock-icon--anim-${this.state.unlockAnim}`}> | ||
| {this.state.unlockAnim < 2 ? <LockClosedIcon /> : <LockOpenIcon />} | ||
| </div> | ||
| </div> | ||
| <span class={classes('detail-player-locked-message-text', { 'detail-player-locked-message-text--unlocked': this.state.unlockBegin })}> | ||
| {i18n('detail_player_subscribe')} | ||
| </span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| playButton() { | ||
| return ( | ||
| <div class={classes('detail-player-play-button', {'detail-player-play-button--clicked': this.state.playClicked})} | ||
| onClick={() => { | ||
| this.setState({ playClicked: true }); | ||
| this.props.showPlayer(); | ||
| }} | ||
| > | ||
| <PlayIcon /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| playUnlockAnimation() { | ||
| setTimeout(() => { this.setState({ unlockBegin: true }) }, 500); | ||
| setTimeout(() => { this.setState({ unlockAnim: 1 }); }, 1000); | ||
| setTimeout(() => { this.setState({ unlockAnim: 2 }); }, 1800); | ||
| setTimeout(() => { this.setState({ unlockAnim: 3 }); }, 1900); | ||
| setTimeout(() => { this.props.showPlayer() }, 2000); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| function ImageLoader(props) { | ||
| const {src, state, onLoad, children} = props; | ||
| if (!src) { | ||
| return; | ||
| } | ||
| return ( | ||
| <div> | ||
| {!state && <img | ||
| class="detail-player-image-loader" | ||
| src={src} | ||
| onLoad={onLoad()} | ||
| onError={onLoad()} | ||
| />} | ||
| {state && children} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { bindEachComponent, AppComponent, attrs, h, render } from 's72.ui'; | ||
| import { DetailPlayerIframe } from './detail-player-iframe.component'; | ||
| import { DetailPlayerPlaceholder } from './detail-player-placeholder.component'; | ||
|
|
||
| export default class DetailPlayer extends AppComponent { | ||
|
|
||
| constructor(props, context) { | ||
| super(props, context); | ||
| this.state = { | ||
| playerLoaded: false, | ||
| playerVisible: false, | ||
| placeholderVisible: true, | ||
| }; | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| window.addEventListener('message', e => { | ||
| const {event, value} = e.data; | ||
| if (event == 's72-player:theatre-mode-change') { | ||
| const container = document.querySelector('#main .detail-player-container'); | ||
| container.querySelector('detail-player').scrollIntoView({ behavior: 'smooth', block: 'start' }); | ||
| container.classList.toggle('detail-player-theatre-mode', value); | ||
| document.querySelector('.meta-detail-bg').classList.toggle('meta-detail-bg--lights-out', value); | ||
| document.querySelector('.poster-wrapper').classList.toggle('d-none', value); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| render(props, state) { | ||
| if (!props.slug) { | ||
| return; | ||
| } | ||
| return ( | ||
| <div class="detail-player-inner"> | ||
| {state.playerVisible && <DetailPlayerIframe | ||
| slug={props.slug} | ||
| onLoad={() => { | ||
| this.setState({ playerLoaded: true }); | ||
| setTimeout(() => this.setState({ placeholderVisible: false }), 1000); | ||
| }} | ||
| />} | ||
| {state.placeholderVisible && <DetailPlayerPlaceholder | ||
| slug={props.slug} | ||
| bgImage={props.bgImage} | ||
| titleImage={props.titleImage} | ||
| showPlayer={() => setTimeout(() => this.setState({ playerVisible: true }), 500)} | ||
| playerLoaded={state.playerLoaded} | ||
| />} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| bindEachComponent('detail-player', e => render(h(DetailPlayer, attrs(e)), e)); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ import './modernizr-custom.js'; | |
| import './can-be-watched-button.component.js'; | ||
| import './external-purchase-button.component.js'; | ||
| import './carousel-video-mute-button.component.js'; | ||
| import './detail-player/detail-player.component.js'; | ||
|
|
||
| /*global Swiper, Modernizr, s72*/ | ||
|
|
||
|
|
@@ -567,3 +568,11 @@ document.addEventListener('s72loaded', event => { | |
| let app = event.detail.app; | ||
| documentReady(app); | ||
| }); | ||
|
|
||
| window.addEventListener('message', e => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be added to the componentDidMount of the detail player component instead? That would keep the code together. Even if we were building a proper SPA we'd add this event listener when the detail player mounts, and then unsubscribe in componentDidUnmount, but our Relish components are all sloppy about that |
||
| const {event, url, type} = e.data; | ||
| if (event == 's72-player:navigate-to') { | ||
| console.log(`navigating to ${url}, reason: ${type}`); | ||
| window.location.href = `${url}`; | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -261,7 +261,7 @@ | |
| } | ||
|
|
||
| .caption { | ||
| padding: 0px 0 8px; | ||
| padding: 0 0 8px; | ||
|
|
||
| .crumb { | ||
| @extend .d-flex; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We found a bug here when a site does not have the "disable_anonymous_watch_now" meta config set. It will display a play button for anonymous users and if clicked will open the sign in page within the detail player iframe. For now its prolly fine as we can ensure this config is a prereq for this feature, but might do some wierd things otherwise for other business models.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah there are a bunch of edge cases that still need to be sorted with this feature before it's ready for widespread deployment but I don't think they're blockers for merge.