Skip to content
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

feat: added media player #23

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/integrations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ You can do it, but it requires all optional libraries to be installed tho.
- [LottiePlayer](./lottie-player/README.md), using `lottie-web`
- [SplitText](./split-text/README.md), using `gsap@business`
- [VideoAutoplay](./video-autoplay/README.md)
- [MediaPlayer](./media-player/README.md)

[<- Root](/README.md)
1 change: 1 addition & 0 deletions packages/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './lazy-load';
export * from './lottie-player';
export * from './split-text';
export * from './video-autoplay';
export * from './media-player';
228 changes: 228 additions & 0 deletions packages/integrations/media-player/MediaPlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { LazyLoad, LazyLoadOptions } from '@ovee.js/toolkit-integrations';
import { emitEvent, Logger, register } from 'ovee.js';
import Plyr from 'plyr';

import { MEDIA_PLAYER_DEFAULT_OPTIONS } from './constants';
import { Player, PlayerOptions } from './types';

const logger = new Logger('MediaPlayer');
const players: { element: HTMLElement; player: Player }[] = [];
@register('media-player')
export class MediaPlayer extends LazyLoad {
player: Player;
playPromise: Promise<void>;
loadPromise: Promise<void>;
loadPromiseResolve: CallableFunction;
isPlayerInitialized = false;
isElementVisible = false;

//WithInViewport Options
get observerOptions(): IntersectionObserverInit {
return {
threshold: 0,
};
}

//LazyLoad Options
get options(): LazyLoadOptions {
return {
...super.options,
callback_loaded: () => {
this.loadPromiseResolve?.();
},
};
}

get mediaElement() {
return this.$element as HTMLElement;
}

get isMediaLoaded() {
if (!this.mediaElement) {
return false;
}

const target =
this.mediaElement.tagName === 'DIV'
? this.mediaElement.querySelector<HTMLElement>('iframe')
: this.mediaElement;

if (!target) {
return false;
}

return target.classList.contains('loaded') || target.dataset?.llStatus === 'loaded';
}

get jsonConfig(): PlayerOptions {
const { mediaElement, isPlayerInitialized } = this;

if (!mediaElement) {
return {};
}

const config =
mediaElement.getAttribute('data-plyr-config') ||
mediaElement.getAttribute('data-player-config');

if (!config) {
return {};
}

if (!isPlayerInitialized) {
mediaElement.setAttribute('data-player-config', config);
mediaElement.removeAttribute('data-plyr-config');
}

try {
return JSON.parse(config);
} catch {
logger.warn('Invalid JSON Config:', config);

return {};
}
}

get playerConfig(): PlayerOptions {
const { $options, jsonConfig } = this;

return {
...MEDIA_PLAYER_DEFAULT_OPTIONS,
...$options,
...jsonConfig,
};
}

get autoplayEnabled() {
const { playerConfig, player } = this;

return (
(!player?.isYouTube && !player?.isVimeo && playerConfig.autoplay) ||
(player?.isYouTube && playerConfig.youtube?.autoplay) ||
(player?.isVimeo && playerConfig.vimeo?.autoplay)
);
}

init() {
super.init();

this.isLoadingInitialized = this.mediaElement.dataset.src === undefined;

this.createLoadPromise();
this.createPlayer();
this.bind();
}

createLoadPromise() {
this.loadPromise = new Promise(resolve => {
if (this.isLoadingInitialized) {
this.$element.classList.add(this.options?.class_loaded ?? 'loaded');

resolve();
} else {
this.loadPromiseResolve = resolve;

this.load();
}
});
}

async createPlayer() {
await this.loadPromise;

const { playerConfig, mediaElement } = this;

if (!mediaElement) {
return;
}

this.player = new Plyr(mediaElement, playerConfig) as Player;

players.push({
element: mediaElement,
player: this.player,
});

if (mediaElement?.tagName === 'AUDIO') {
mediaElement.setAttribute('controls', '');
}
}

play() {
if (!this.isPlayerInitialized) {
return;
}

this.playPromise = this.player?.play() ?? Promise.resolve();
}

async pause() {
if (!this.isPlayerInitialized) {
return;
}

await this.playPromise;

this.player?.pause();
}

onIntersection(entry: IntersectionObserverEntry) {
super.onIntersection(entry);

if (entry.isIntersecting) {
if (!this.autoplayEnabled) {
return;
}

this.play();
} else {
this.pause();
}

this.isElementVisible = entry.isIntersecting;
}

onPlayerReady() {
this.isPlayerInitialized = true;

emitEvent(this.mediaElement, 'media-player:initialized');

if (!this.autoplayEnabled || !this.isElementVisible) {
return;
}

this.player.play();
this.player.volume = 0;
}

onPlay() {
const { mediaElement } = this;

players
.filter(({ element }) => !element.isSameNode(mediaElement))
.forEach(({ player }) => {
player.pause();
});

emitEvent(mediaElement, 'media-player:played');
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
onPause() {
emitEvent(this.mediaElement, 'media-player:paused');
}

bind() {
this.$on('media-player:play', this.mediaElement, this.play);
this.$on('media-player:pause', this.mediaElement, this.pause);
this.$on('play', this.mediaElement, this.onPlay);
this.$on('pause', this.mediaElement, this.onPause);
this.$on('ready', this.mediaElement, this.onPlayerReady);
}

destroy() {
players.splice(0, players.length);
this.player?.destroy();
super.destroy();
}
}
115 changes: 115 additions & 0 deletions packages/integrations/media-player/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Medialayer

## Requirements
- [plyr](https://www.npmjs.com/package/plyr)

```bash
yarn add plyr
```

## Installation and configuration

See [Components instalation](/docs/components_instalation.md)

## Usage example

Basic usage

```html

<!-- Without LazyLoad -->
<video
src="file.mp4"
data-media-player
></video>

<!-- With LazyLoad -->
<video
data-src="file.mp4"
data-media-player
></video>

<!-- YouTube/Vimeo -->
<div class="video-iframe" data-media-player>
<iframe
src="https://www.youtube.com/embed/__ID__"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen=""
title="__TITLE__"
></iframe>
</div>

<!-- Audio -->
<audio
src="file.mp3"
data-media-player
data-player-config='{
"autoplay": true,
"muted": true,
"loop": {
"active": true
}
}'
controls
></audio>
```

This component is a wrapper for `plyr` library. Here is the full documentation: [https://github.com/sampotts/plyr]().

To change options you can extend component and override `get playerOptions` property. Remember to call `super.init()` and `super.destroy()` when overriding `init` and `destroy` hooks.

Example:

```ts
export class CustomMediaPlayer extends MediaPlayer {
get playerOptions() {
return {
loop: {
active: true
}
}
}
}

```

or you can add `data-player-config` / `data-plyr-config` to element with config as JSON:

```html
<video
src="file.mp4"
data-media-player
data-player-config='{
"loop": {
"active": true,
},
"volume": 0
}'
></video>
```

You can also pass global options while registering a component:

```ts
const app = new App({
...
});

app.registerComponent(MediaPlayer, {
loop: {
active: true,
},
volume: 0
});
```

## Attributes

| Attribute | Type | Default | Description |
| -- | -- | -- | -- |
| `data-player-config` / `data-plyr-config` | `string (JSON)` | - | [plyr options](https://github.com/sampotts/plyr#options) |

### Extends

[LazyLoad](/src/components/utils/lazy-load/README.md)
31 changes: 31 additions & 0 deletions packages/integrations/media-player/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PlayerOptions } from './types';

export const MEDIA_PLAYER_DEFAULT_OPTIONS: PlayerOptions = {
autoplay: false,
muted: false,
vimeo: {
loop: false,
autoplay: false,
muted: false,
controls: false,
playsinline: false,
byline: false,
portrait: false,
title: false,
speed: true,
transparent: true,
},
youtube: {
autoplay: 0,
controls: 0,
disablekb: 1,
playsinline: 0,
enablejsapi: 1,
origin: window.location.origin,
modestbranding: 1,
cc_load_policy: 1,
rel: 0,
showinfo: 0,
iv_load_policy: 3,
},
};
2 changes: 2 additions & 0 deletions packages/integrations/media-player/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './MediaPlayer';
export * from './types';
Loading