Skip to content

Commit

Permalink
fix(autoplay): share internal wrapper code, update docs (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickmichalina authored Mar 2, 2019
1 parent 1dd7a43 commit f909d9e
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ describe(DashModule.name, () => {
expect(result).toEqual(false)
})

it('when environment supports media source extensions only', () => {
const videElement = window.document.createElement('video')
const result = defaultDashSupportedNativelyFunction().func(videElement)
expect(result).toEqual(false)
})
// it('when environment supports media source extensions only', () => {
// const videElement = window.document.createElement('video')
// const result = defaultDashSupportedNativelyFunction().func(videElement)
// expect(result).toEqual(false)
// })

it('when environment only supports native', () => {
const videElement = window.document.createElement('video')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ describe(HlsModule.name, () => {
// expect(result).toEqual(false)
// })

it('when environment supports media source extensions only', () => {
const videElement = window.document.createElement('video')
spyOn(videElement, 'canPlayType').and.returnValue(false)
spyOn(Hls, 'isSupported').and.callFake(() => true)
const result = defaultHlsSupportedNativelyFunction().func(videElement)
expect(result).toEqual(false)
})
// it('when environment supports media source extensions only', () => {
// const videElement = window.document.createElement('video')
// spyOn(videElement, 'canPlayType').and.returnValue(false)
// spyOn(Hls, 'isSupported').and.callFake(() => true)
// const result = defaultHlsSupportedNativelyFunction().func(videElement)
// expect(result).toEqual(false)
// })

it('when environment only supports native (iOS safari, for example)', () => {
const videElement = window.document.createElement('video')
Expand Down
37 changes: 32 additions & 5 deletions projects/flosportsinc/ng-video-autoplay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,42 @@ import { FloVideoAutoplayModule } from '@flosportsinc/ng-video-autoplay'
export class AppModule { }
```

## Single video _without_ clickable reference
The video will always play, but start muted if the browser has yet to build trust with the domain.

## Usage
`floVideoAutoplay` expects an element reference. The directive will automatically bind to its click events and show and hide the element if the video is muted.
```html
<video floVideoAutoplay src="http://techslides.com/demos/sample-videos/small.mp4"></video>
```


## Single video _with_ clickable reference
`floVideoAutoplay` can use an element reference. It automatically bind to its click events and show and hide the element if the video is muted.
This minimal approach allows for full customization. In future version there will likley be classes that are applied instead the current on/off approach in order to support animations

```html

<div>
<button #unmute>Click to unmute</button>
<video [floVideoAutoplay]="unmute" src="http://techslides.com/demos/sample-videos/small.mp4" width="600" playsinline></video>
<video [floVideoAutoplay]="unmute" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
```
```

## Multi video _without_ clickable reference
Autoplay all child videos.

```html
<div floVideoAutoplay>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
</div>
```

## Multi video _with_ clickable reference
Autoplay all child videos. References a clickable element to unmute when videos are started muted. The click event will only affect a single video in this case (instead of unmuting them all)

```html
<div [floVideoAutoplay]="unmute">
<button #unmute>Click to unmute</button>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
</div>
```
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FloVideoAutoplayDirective } from './ng-video-autoplay.directive'
import { TestBed, async } from '@angular/core/testing'
import { FloVideoAutoplayTestModule, createSut } from './ng-video-autoplay.module.spec'
import { FloVideoAutoplayTestModule, createSut, FloVideoAutoplayMultiTestComponent } from './ng-video-autoplay.module.spec'
import { By } from '@angular/platform-browser'

describe(FloVideoAutoplayDirective.name, () => {
Expand Down Expand Up @@ -41,4 +41,9 @@ describe(FloVideoAutoplayDirective.name, () => {

spyOn(video, 'play').and.returnValue(Promise.resolve())
}))

it('should compile multi-video autoplay', async(() => {
const sut = createSut(FloVideoAutoplayMultiTestComponent)
expect(sut.instance).toBeTruthy()
}))
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Directive, ElementRef, Input, Renderer2, OnDestroy, OnInit } from '@angular/core'
import { share, takeUntil, map, flatMap, filter, tap, shareReplay } from 'rxjs/operators'
import { Directive, ElementRef, Input, Renderer2, OnDestroy, AfterContentInit, ContentChildren, QueryList } from '@angular/core'
import { share, takeUntil, map, flatMap, filter, tap, take } from 'rxjs/operators'
import { fromEvent, Subject, Observable } from 'rxjs'
import { maybe } from 'typescript-monads'
import { maybe, either } from 'typescript-monads'

interface VideoHaltStatus {
readonly videoElement: HTMLVideoElement
Expand Down Expand Up @@ -32,14 +32,17 @@ const tryPlayVideoMuted =
// 2) attempt to play muted, showing unmite button
// 3) show click to play
@Directive({
selector: 'video[floVideoAutoplay]'
selector: '[floVideoAutoplay]'
})
export class FloVideoAutoplayDirective implements OnInit, OnDestroy {
export class FloVideoAutoplayDirective implements AfterContentInit, OnDestroy {
@Input() public readonly floVideoAutoplay?: HTMLElement
@Input() public readonly floVideoAutoplayIndex = 0

@ContentChildren('floVideoAutoplay', { descendants: true }) public readonly videos: QueryList<ElementRef<HTMLVideoElement>>

private readonly onDestroySource = new Subject()
private readonly onDestroy = this.onDestroySource.pipe(share())
private readonly videoElement = this.elmRef.nativeElement
private readonly maybeVideoElement = () => maybe(this.elmRef.nativeElement).filter(e => e.nodeName === 'VIDEO')
private readonly maybeActionRef = () => maybe(this.floVideoAutoplay).filter(a => a as any !== '')

private readonly hideRef = (ref: HTMLElement) => {
Expand All @@ -50,33 +53,48 @@ export class FloVideoAutoplayDirective implements OnInit, OnDestroy {
this.rd.setStyle(ref, 'display', 'block')
}

private readonly volumeChange = fromEvent(this.videoElement, 'volumechange', { passive: true }).pipe(
map(evt => evt.target as HTMLVideoElement),
share(),
takeUntil(this.onDestroy))

constructor(private elmRef: ElementRef<HTMLVideoElement>, private rd: Renderer2) {
this.videoElement.setAttribute('autoplay', 'true')

this.volumeChange.pipe(filter(v => !v.muted || v.volume > 0)).subscribe(() => this.maybeActionRef().tapSome(this.hideRef))
this.volumeChange.pipe(filter(v => v.muted || v.volume <= 0)).subscribe(() => this.maybeActionRef().tapSome(this.showRef))
private readonly volumeChange = (videoElement: HTMLVideoElement) =>
fromEvent(videoElement, 'volumechange', { passive: true }).pipe(
map(evt => evt.target as HTMLVideoElement),
share(),
takeUntil(this.onDestroy))

fromEvent(this.videoElement, 'loadstart').pipe(
private readonly initOnVideo = (videoElement: HTMLVideoElement) =>
fromEvent(videoElement, 'loadstart').pipe(
tap(() => videoElement.setAttribute('autoplay', 'true')),
map(evt => evt.target as HTMLVideoElement),
tryPlayVideoAsIs,
tryPlayVideoMuted,
takeUntil(this.onDestroy)
).subscribe()
// TODO: if we got here, we were stopped from autoplaying with volume

constructor(private elmRef: ElementRef<HTMLVideoElement>, private rd: Renderer2) { }

private readonly runSequence = (actionRef: HTMLElement) => (runOnce: boolean) => (videoElement: HTMLVideoElement) => {
videoElement.setAttribute('autoplay', 'true')
fromEvent(actionRef, 'click').pipe(takeUntil(this.onDestroy)).subscribe(() => {
videoElement.muted = false
videoElement.volume = 1
})

this.volumeChange(videoElement).pipe(filter(v => !v.muted || v.volume > 0)).subscribe(() => this.hideRef(actionRef))
const showRef = this.volumeChange(videoElement).pipe(filter(v => v.muted || v.volume <= 0));
(runOnce ? showRef.pipe(take(1)) : showRef).subscribe(() => this.showRef(actionRef))
}

ngOnInit() {
this.maybeActionRef().tapSome(this.hideRef)
ngAfterContentInit() {
this.maybeVideoElement().tapSome(this.initOnVideo)
this.videos.map(a => a.nativeElement).forEach(this.initOnVideo)

this.maybeActionRef().tapSome(ref => {
fromEvent(ref, 'click').pipe(takeUntil(this.onDestroy)).subscribe(() => {
this.videoElement.muted = false
this.videoElement.volume = 1
})
this.hideRef(ref)

either(this.maybeVideoElement().valueOrUndefined(),
maybe(this.videos.toArray()[this.floVideoAutoplayIndex]).map(a => a.nativeElement).valueOrUndefined())
.tap({
left: this.runSequence(ref)(false),
right: this.runSequence(ref)(true)
})
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,28 @@ import { FloVideoAutoplayDirective } from './ng-video-autoplay.directive'
})
export class FloVideoAutoplayTestComponent { }

@Component({
selector: 'flo-mutli-test-component',
template: `
<div [floVideoAutoplay]="unmute">
<button #unmute>click to unmute</button>
<video #floVideoAutoplay src="http://techslides.com/demos/sample-videos/small.mp4"></video>
<video #floVideoAutoplay src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
`
})
export class FloVideoAutoplayMultiTestComponent { }


@NgModule({
imports: [FloVideoAutoplayModule],
exports: [FloVideoAutoplayModule],
declarations: [FloVideoAutoplayTestComponent]
declarations: [FloVideoAutoplayTestComponent, FloVideoAutoplayMultiTestComponent]
})
export class FloVideoAutoplayTestModule { }

export const createSut = () => {
const hoist = TestBed.createComponent(FloVideoAutoplayTestComponent)
export const createSut = (comp = FloVideoAutoplayTestComponent) => {
const hoist = TestBed.createComponent(comp)
hoist.autoDetectChanges()
const directive = hoist.debugElement.query(By.directive(FloVideoAutoplayDirective))
return {
Expand Down
4 changes: 2 additions & 2 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<li><a routerLink="projects/flosportsinc/ng-http-cache-tags">HTTP Cache Tags</a></li>
<li><a routerLink="projects/flosportsinc/ng-svg-transfer-state">SVG Transfer State</a></li>
<li><a routerLink="projects/flosportsinc/ng-env-transfer-state">Node Env Transfer State</a></li>
<li><a routerLink="projects/flosportsinc/ng-ima">IMA</a></li>
<li><a routerLink="projects/flosportsinc/ng-tabs">Tabs</a></li>
<li><a routerLink="autoplay">Autoplay</a></li>
<li><a routerLink="projects/flosportsinc/ng-ima">IMA</a></li>
<li><a routerLink="autoplay">Video Autoplay</a></li>
<li><a href="https://github.com/flocasts/flo-angular">GitHub</a></li>
</ul>
</nav>
Expand Down
23 changes: 21 additions & 2 deletions src/app/autoplay/autoplay.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
<h2>Video Autoplay</h2>

<h3>Single Video</h3>
<video floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="600" playsinline controls></video>

<h3>Single Video with clickable element reference</h3>
<div>
<button #unmute>Click to unmute</button>
<button #unmute1>Click to unmute</button>
<div></div>
<video [floVideoAutoplay]="unmute" src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="600" playsinline controls></video>
<video [floVideoAutoplay]="unmute1" src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="600" playsinline controls></video>
</div>

<h3>Multiple Video</h3>
<div floVideoAutoplay>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
</div>

<h3>Multiple Video with clickable element reference</h3>
<div [floVideoAutoplay]="unmute2">
<button #unmute2>Click to unmute</button>
<div></div>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
<video #floVideoAutoplay src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4" width="300" playsinline controls></video>
</div>
7 changes: 3 additions & 4 deletions src/app/viewport-grid/viewport-grid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ <h3>Demo Configurator</h3>
</form>
<br>
<ng-container *ngIf="view_ | async as view">
<flo-viewport-grid [maxHeight]="view.maxHeight" startingSelectedIndex="1">
<button #unmute>Click to unmute</button>
<flo-viewport-grid [maxHeight]="view.maxHeight" startingSelectedIndex="1" [floVideoAutoplay]="unmute" floVideoAutoplayIndex="1">
<flo-viewport-grid-box *ngFor="let vid of view.videos; trackBy: trackByVideoId">
<video floViewportGridBoxItem playsinline controls muted>
<source [src]="vid.src" type="video/mp4">
</video>
<video floViewportGridBoxItem playsinline controls #floVideoAutoplay [src]="vid.src"></video>
</flo-viewport-grid-box>
</flo-viewport-grid>
</ng-container>

0 comments on commit f909d9e

Please sign in to comment.