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/audio setup #232

Merged
merged 12 commits into from
Sep 18, 2023
7 changes: 7 additions & 0 deletions .changeset/eighty-toys-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@dotlottie/player-component': minor
'@dotlottie/react-player': minor
'@dotlottie/common': minor
---

automatic audio detection and support
Binary file added apps/react-player-test/public/audio.lottie
Binary file not shown.
4 changes: 3 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@dotlottie/dotlottie-js": "0.5.2",
"@dotlottie/dotlottie-js": "0.6.0",
"@lottiefiles/relottie": "1.0.0",
"@lottiefiles/relottie-style": "0.4.1",
"@preact/signals-core": "^1.2.3",
"howler": "^2.2.3",
"lottie-web": "^5.12.2",
"xstate": "^4.38.1"
},
"devDependencies": {
"@lottiefiles/lottie-types": "^1.2.0",
"@types/howler": "^2.2.8",
"tsup": "^6.1.3",
"typescript": "^4.7.4"
},
Expand Down
47 changes: 47 additions & 0 deletions packages/common/src/dotlottie-audio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import { Howl } from 'howler';

export interface DotLottieAudioOptions {
src: string[];
}

export class DotLottieAudio {
private readonly _howl: Howl;

public constructor({ src }: DotLottieAudioOptions) {
this._howl = new Howl({
src,
});
}

public play(): number {
return this._howl.play();
}

public pause(): Howl {
return this._howl.pause();
}

public playing(): boolean {
return this._howl.playing();
}

public rate(): number {
return this._howl.rate();
}

public seek(): number {
return this._howl.seek();
}

public setVolume(): number {
return this._howl.volume();
}

public unload(): void {
this._howl.unload();
}
}
59 changes: 52 additions & 7 deletions packages/common/src/dotlottie-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@

import pkg from '../package.json';

import type { DotLottieAudio } from './dotlottie-audio';
import { DotLottieLoader } from './dotlottie-loader';
import { loadStateMachines } from './dotlottie-state-machine-loader';
import { applyLottieStyleSheet } from './dotlottie-styler';
import type { DotLottieStateMachineManager } from './state/dotlottie-state-machine-manager';
import { Store } from './store';
import { createError, isValidLottieJSON, isValidLottieString, logError, logWarning } from './utils';
import {
createError,
isValidLottieJSON,
isValidLottieString,
logError,
logWarning,
lottieContainsAudio,
} from './utils';

export type { AnimationDirection, AnimationItem, AnimationSegment };

Expand Down Expand Up @@ -197,6 +205,8 @@

private _visibilityPercentage: number = 0;

private _audios: DotLottieAudio[] = [];

protected _stateMachineManager?: DotLottieStateMachineManager;

public constructor(
Expand Down Expand Up @@ -356,7 +366,7 @@
const options = getOptions(this._getPlaybackOptions());

try {
PlaybackOptionsSchema.parse(options);
PlaybackOptionsSchema._parse(options);
} catch (error) {
logWarning(`Invalid PlaybackOptions, ${JSON.stringify(options, null, 2)}`);

Expand Down Expand Up @@ -1232,6 +1242,14 @@
this._container.__lottie = null;
}

if (this._audios.length) {
// Loop over the instances and unload
this._audios.forEach((instance) => {
instance.unload();
});
this._audios = [];
}

this.clearCountTimer();
if (typeof document !== 'undefined') {
document.removeEventListener('visibilitychange', () => this._onVisibilityChange());
Expand Down Expand Up @@ -1743,11 +1761,38 @@
loop,
}));

this._lottie = lottiePlayer.loadAnimation({
...options,
container: this._container as Element,
animationData: this._animation,
});
let audioFactory;

if (this._animation && lottieContainsAudio(this._animation)) {
const { DotLottieAudio } = await import('./dotlottie-audio');

const howl = (assetPath: string): DotLottieAudio => {
const audioInstance = new DotLottieAudio({
src: [assetPath],
});

this._audios.push(audioInstance);

return audioInstance;
};

audioFactory = howl;
}

if (audioFactory) {
this._lottie = lottiePlayer.loadAnimation({
...options,
container: this._container as Element,
animationData: this._animation,
audioFactory,
});
} else {
this._lottie = lottiePlayer.loadAnimation({
...options,
container: this._container as Element,
animationData: this._animation,
});
}

// Define our own reset segments for worker if its un-implemented
// eslint-disable-next-line no-secrets/no-secrets
Expand Down Expand Up @@ -1789,7 +1834,7 @@
'Worker is only supported with svg renderer. Change or remove renderer prop to get rid of this warning.',
);
}
// @ts-ignore

Check warning on line 1837 in packages/common/src/dotlottie-player.ts

View workflow job for this annotation

GitHub Actions / validate

Do not use "@ts-ignore" because it alters compilation errors
LottieWebModule = await import(`lottie-web/build/player/lottie_worker`);

return LottieWebModule.default;
Expand All @@ -1809,7 +1854,7 @@
if (this._light) {
LottieWebModule = await import(`lottie-web/build/player/lottie_light_canvas`);
} else {
// @ts-ignore

Check warning on line 1857 in packages/common/src/dotlottie-player.ts

View workflow job for this annotation

GitHub Actions / validate

Do not use "@ts-ignore" because it alters compilation errors
LottieWebModule = await import(`lottie-web/build/player/lottie_canvas`);
}

Expand Down
15 changes: 15 additions & 0 deletions packages/common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Copyright 2023 Design Barn Inc.
*/

import { isAudioAsset } from '@dotlottie/dotlottie-js';
import type { Animation, Asset } from '@lottiefiles/lottie-types';

export function createError(error: string, prefix = 'dotLottie-common'): Error {
const err = new Error(`[${prefix}]: ${error}`);

Expand Down Expand Up @@ -38,6 +41,18 @@ export function isValidLottieJSON(json: Record<string, unknown>): boolean {
return mandatory.every((field: string) => Object.prototype.hasOwnProperty.call(json, field));
}

export function lottieContainsAudio(json: Animation): boolean {
const assets: Asset.Value[] | undefined = json.assets;

if (assets) {
return assets.some((asset: Asset.Value) => {
return isAudioAsset(asset);
});
}

return false;
}

export function isValidLottieString(str: string): boolean {
try {
const json = JSON.parse(str);
Expand Down
30 changes: 30 additions & 0 deletions packages/player-component/cypress/component/audio.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import { html } from 'lit';

describe('Audio', () => {
it('Howler should be present in the window if theres audio inside the animation', () => {
cy.mount(
html`
<dotlottie-player speed=10 data-testid="testPlayer" autoplay loop controls style="height: 200px;" src="/audio.lottie">
</dotlottie-player>
`,
);

cy.window().its('Howler').its('_howls').should('have.length', 3);
cy.window().should('have.property', 'Howl');
});

it('Howler should not have any howls loaded if the active animation is changed', () => {
cy.mount(
html`
<dotlottie-player data-testid="testPlayer" autoplay loop controls style="height: 200px;" src="/cool-dog.lottie">
</dotlottie-player>
`,
);

cy.window().its('Howler').its('_howls').should('have.length', 0);
});
});
18 changes: 18 additions & 0 deletions packages/player-component/cypress/component/no-audio.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright 2023 Design Barn Inc.
*/

import { html } from 'lit';

describe('No audio', () => {
it('Howler should not be present in the window if theres audio inside the animation', () => {
cy.mount(
html`
<dotlottie-player data-testid="testPlayer" autoplay loop controls style="height: 200px;" src="/cool-dog.lottie">
</dotlottie-player>
`,
);

cy.window().should('not.have.property', 'Howler');
});
})
Binary file not shown.
4 changes: 4 additions & 0 deletions packages/player-component/src/dotlottie-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@
return;
}

if (this._dotLottieCommonPlayer) {
this._dotLottieCommonPlayer.destroy();
}

/**
* User's can call the load method - only do new initialization inside firstConnected()
*/
Expand All @@ -312,7 +316,7 @@
hover: this.hasAttribute('hover') ? this.hover : undefined,
renderer: this.hasAttribute('renderer') ? this._renderer : undefined,
loop: this.hasAttribute('loop') ? this._loop : undefined,
direction: this.hasAttribute('direction') ? (this.direction === 1 ? 1 : -1) : undefined,

Check warning on line 319 in packages/player-component/src/dotlottie-player.ts

View workflow job for this annotation

GitHub Actions / validate

Do not nest ternary expressions
speed: this.hasAttribute('speed') ? this.speed : undefined,
intermission: this.hasAttribute('intermission') ? Number(this.intermission) : undefined,
playMode: this.hasAttribute('playMode') ? this.playMode : undefined,
Expand Down
114 changes: 114 additions & 0 deletions packages/react-player/cypress/component/audio.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// /**
// * Copyright 2023 Design Barn Inc.
// */

// import { useRef, useState, ReactDOM } from 'react';
// import { DotLottieRefProps } from '../../dist';
// import { Controls } from '../../src/controls';
// import { DotLottiePlayer } from '../../src/react-player';
// import { PlayerStateWrapper } from '../support/player-state-wrapper';

// describe('Audio', () => {
// // it('Howl should be present in the window if theres audio inside the animation', () => {
// // cy.mount(
// // <PlayerStateWrapper>
// // <DotLottiePlayer id='player' speed={10} src={`/audio.lottie`} style={{ height: '400px', display: 'inline-block' }} autoplay>
// // <Controls />
// // </DotLottiePlayer>
// // ,
// // </PlayerStateWrapper>,
// // );

// // cy.window().should('have.property', 'Howl');
// // cy.window().its('Howler').its('_howls').should('have.length', 3);

// // cy.get('#player').invoke('remove')
// // });

// it('Should unload howls if the src is changed', () => {
// function Wrapper(): JSX.Element {
// const lottieRef = useRef<DotLottieRefProps>();
// const [src, setSrc] = useState<string>('/audio.lottie');

// return (
// <>
// <button
// data-testid="next"
// onClick={(): void => {
// setSrc('/cool-dog.lottie');
// }}
// >
// Change src
// </button>
// <PlayerStateWrapper
// onRef={(ref: DotLottieRefProps) => {
// lottieRef.current = ref;
// }}
// >
// <DotLottiePlayer id='player' speed={10} src={src} style={{ height: '400px', display: 'inline-block' }} autoplay>
// <Controls />
// </DotLottiePlayer>
// </PlayerStateWrapper>
// </>
// );
// }

// cy.mount(<Wrapper />);

// cy.window().should('have.property', 'Howl');
// cy.window().its('Howler').its('_howls').should('have.length', 3);

// cy.get('[data-testid="next"]').click({ force: true });

// cy.window().should('have.property', 'Howl');
// cy.window().its('Howler').its('_howls').should('have.length', 0);

// cy.get('[name="currentAnimationId"]').should('have.value', '22350275-2e75-41e9-964e-40a766d44237');
// })

// it('Should unload howls if element is unmounted', () => {
// function Wrapper(): JSX.Element {
// const lottieRef = useRef<DotLottieRefProps>();
// const [src, setSrc] = useState<string>('/audio.lottie');
// const [display, setDisplay] = useState<boolean>(true);
// const elem = <>
// <DotLottiePlayer lottieRef={lottieRef} id='player' speed={10} src={src} style={{ height: '400px', display: 'inline-block' }} autoplay>
// <Controls />
// </DotLottiePlayer>

// </>

// return (
// <>
// <button
// data-testid="next"
// onClick={(): void => {
// setDisplay(false);
// }}
// >
// Change src
// </button>
// {
// display && (
// <>
// {
// elem
// }
// </>
// )
// }
// </>
// );
// }
// cy.mount(<Wrapper />);

// cy.window().should('have.property', 'Howl');
// cy.window().its('Howler').its('_howls').should('have.length', 3);

// cy.get('[data-testid="next"]').click({ force: true });

// cy.window().should('have.property', 'Howl');
// cy.window().its('Howler').its('_howls').should('have.length', 0);

// })
// });
Loading
Loading