Skip to content

Commit

Permalink
Feat/audio (#52)
Browse files Browse the repository at this point in the history
* feat: added audio asset support

* chore: pnpm lock

* chore: new generator file

* fix: tests

* chore: changeset

* chore: formatting

* fix: various fixes

* fix: tests

* chore: fixes
  • Loading branch information
samuelOsborne committed Sep 7, 2023
1 parent 84d03bf commit fcbe995
Show file tree
Hide file tree
Showing 23 changed files with 1,248 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/pink-bottles-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dotlottie/dotlottie-js": minor
---

adds audio asset support
126 changes: 126 additions & 0 deletions apps/node/audio_test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Copyright 2023 Design Barn Inc.
*/

/**
* Current status:
* - Exporting correctly as a dotLottie
* - todo: test if it plays
* - todo: multi asset support
*/

import fs from 'fs';

import { DotLottie } from '@dotlottie/dotlottie-js/node';
import { getAnimation, getAudio, getAllAudio } from '@dotlottie/dotlottie-js/node';

// const dotLottie = new DotLottie();

// const dl = await
// dotLottie
// .setAuthor('Sam!')
// .setVersion('1.0')
// .addAnimation({
// id: 'audio',
// // url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json',
// url: 'https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json'
// })
// .build()
// .then((value) => {
// return value.toArrayBuffer();
// })
// .then(async (value) => {
// const filename = 'multiple_audio.lottie';

// console.log('> Writing to file: ', filename);

// fs.writeFileSync(filename, Buffer.from(value));

// console.log('> Writing inlined file from getAnimation 🪄');

// const anim = await getAnimation(new Uint8Array(value), 'audio', {
// inlineAssets: true
// });

// fs.writeFileSync("audio_inlined.json", JSON.stringify(anim));

// // -------–––-------–––-------–––-------–––-------–––-------–––

// console.log('> Using DotLottie.fromArrayBuffer 🪄');

// let ad = new DotLottie();

// ad = await ad.fromArrayBuffer(value);

// fs.writeFileSync("audio_inlined_from_array.json", Buffer.from(await ad.toArrayBuffer()));

// // console.log(ad);
// // console.log("\n\n\n\n\n")

// // console.log(await ad.getAnimation('audio'));
// // console.log(await ad.getAnimation('audio').audioAssets);
// });

const double = new DotLottie();

const doubleAnimation = await double
.setAuthor('Sam!')
.setVersion('1.0')
.addAnimation({
id: 'audio_0',
// url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json',
url: 'https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json',
})
.addAnimation({
id: 'audio_1',
// url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json',
// url: 'https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json'
url: 'https://lottie.host/a9b21c95-dfb7-457e-9916-f6b7942d7479/CtLEGpLFCs.json',
})
// .addAnimation({
// id: 'audio',
// // url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json',
// // url: 'https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json'
// url: 'https://lottie.host/a9b21c95-dfb7-457e-9916-f6b7942d7479/CtLEGpLFCs.json'
// })
.build()
.then((value) => {
return value.toArrayBuffer();
})
.then(async (value) => {
const filename = 'double_audio_animation.lottie';

console.log('> Writing to file: ', filename);

fs.writeFileSync(filename, Buffer.from(value));

const audio = await getAudio(new Uint8Array(value), 'audio_1.mpeg');
const allAudio = await getAllAudio(new Uint8Array(value));
console.log('Received audio:');
console.log(audio);

// console.log(allAudio)

// let ad = new DotLottie();

// ad = await ad.fromArrayBuffer(value);

// const audio_0 = await ad.getAnimation('audio_0');
// const audio_1 = await ad.getAnimation('audio_1');

// fs.writeFileSync("audio_0.json", JSON.stringify(await audio_0.toJSON({
// inlineAssets: true
// })));
// fs.writeFileSync("audio_1.json", JSON.stringify(await audio_1.toJSON({
// inlineAssets: true
// })));

const anim_0 = await getAnimation(new Uint8Array(value), 'audio_0', {
inlineAssets: true,
});
const anim_1 = await getAnimation(new Uint8Array(value), 'audio_1', {
inlineAssets: true,
});
fs.writeFileSync('anim_0.json', JSON.stringify(anim_0));
fs.writeFileSync('anim_1.json', JSON.stringify(anim_1));
});
65 changes: 65 additions & 0 deletions apps/player/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<title>Player</title>
<!-- <link rel="stylesheet" type="text/css" href="css/player.css" /> -->
</head>
<style>
.container {
display: block;
margin: auto;
width: 100%;
height: 400px;
background-color: black;
}
#p-container {
margin-top: 300px;
display: block;
margin: auto;
width: 512px;
height: 512px;
background-color: cyan;
}
</style>

<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.0/howler.min.js"></script>
<script src="https://unpkg.com/@lottiefiles/lottie-player@1.5.7/dist/lottie-player.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.11/lottie.min.js"></script>
<script src="https://unpkg.com/@dotlottie/player-component@2.5.1/dist/dotlottie-player.js"></script>
<body>
<div style="display: flex">
<div id="animation-container" class="container"></div>
<!-- <lottie-player
class="p-container"
src="https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json"
controls
></lottie-player>
<dotlottie-player class="p-container" src="double_audio_animation.lottie" controls></dotlottie-player> -->

<!--------- -->
<lottie-player class="p-container" src="./anim_0.json" controls></lottie-player>
<dotlottie-player class="p-container" src="./anim_0.json" controls></dotlottie-player>

<lottie-player class="p-container" src="./anim_1.json" controls></lottie-player>
<dotlottie-player class="p-container" src="./anim_1.json" controls></dotlottie-player>
</div>
</body>
<script>
let animContainer = document.getElementById('animation-container');

// let anim = lottie.loadAnimation({
// container: animContainer,
// name: 'lottie-web-test',
// renderer: 'svg',
// loop: false,
// autoplay: false,
// path: 'https://lottie.host/d9f7a848-50ab-4d42-805f-0e5a5f686ebe/6JE9gErlEZ.json',

// // path:'https://lottie.host/946d542b-63da-44ba-9d27-700872055cf3/LU83buFliM.json'
// });

animContainer.addEventListener('click', () => {
anim.goToAndPlay(0, true);
});
</script>
</html>
92 changes: 86 additions & 6 deletions packages/dotlottie-js/src/common/dotlottie-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { DotLottieStateMachineCommon } from './dotlottie-state-machine-common';
import type { ThemeOptions } from './dotlottie-theme-common';
import { LottieThemeCommon } from './dotlottie-theme-common';
import type { AnimationOptions, LottieAnimationCommon } from './lottie-animation-common';
import type { LottieAudioCommon } from './lottie-audio-common';
import type { LottieImageCommon } from './lottie-image-common';
import type { Manifest } from './manifest';
import { createError, isValidURL } from './utils';
import { DotLottieError, createError, isAudioAsset, isImageAsset, isValidURL } from './utils';

export interface DotLottieOptions {
author?: string;
Expand Down Expand Up @@ -273,6 +274,64 @@ export class DotLottieCommon {
}
}

/**
* Renames the underlying LottieAudio, as well as updating the audio asset path inside the animation data.
* @param newName - desired id and fileName,
* @param audioId - The id of the LottieAudio to rename
*/
private _renameAudio(animation: LottieAnimationCommon, newName: string, audioId: string): void {
animation.audioAssets.forEach((audioAsset) => {
if (audioAsset.id === audioId) {
// Rename the LottieImage
audioAsset.renameAudio(newName);

if (!animation.data) throw new DotLottieError('No animation data available.');

const animationAssets = animation.data.assets as AnimationType['assets'];

if (!animationAssets) throw new DotLottieError('No audio assets to rename.');

// Find the audio asset inside the animation data and rename its path
for (const asset of animationAssets) {
if (isAudioAsset(asset)) {
if (asset.id === audioId) {
asset.p = audioAsset.fileName;
}
}
}
}
});
}

private _renameAudioAssets(): void {
const audio: Map<string, LottieAudioCommon[]> = new Map();

this.animations.forEach((animation) => {
audio.set(animation.id, animation.audioAssets);
});

let size = 0;

audio.forEach((value) => {
size += value.length;
});

for (let i = this.animations.length - 1; i >= 0; i -= 1) {
const animation = this.animations.at(i);

if (animation) {
for (let j = animation.audioAssets.length - 1; j >= 0; j -= 1) {
const audioAsset = animation.audioAssets.at(j);

if (audioAsset) {
this._renameAudio(animation, `audio_${size}`, audioAsset.id);
size -= 1;
}
}
}
}
}

protected _addLottieAnimation(animation: LottieAnimationCommon): DotLottieCommon {
if (this._animationsMap.get(animation.id)) {
throw createError('Duplicate animation id detected, aborting.');
Expand All @@ -288,15 +347,16 @@ export class DotLottieCommon {
* @param animation - Animation whose asset are to be inlined
* @returns LottieAnimationCommon with inlined assets
*/
private async _findImageAssetAndInline(animation: LottieAnimationCommon): Promise<LottieAnimationCommon> {
private async _findAssetsAndInline(animation: LottieAnimationCommon): Promise<LottieAnimationCommon> {
const animationAssets = animation.data?.assets as AnimationType['assets'];

if (!animationAssets) throw createError("Failed to inline assets, the animation's assets are undefined.");
if (!animationAssets) throw new DotLottieError("Failed to inline assets, the animation's assets are undefined.");

const images = this.getImages();
const audios = this.getAudio();

for (const asset of animationAssets) {
if ('w' in asset && 'h' in asset && !('xt' in asset) && 'p' in asset) {
if (isImageAsset(asset)) {
for (const image of images) {
if (image.fileName === asset.p) {
// encoded is true
Expand All @@ -305,6 +365,15 @@ export class DotLottieCommon {
asset.p = await image.toDataURL();
}
}
} else if (isAudioAsset(asset)) {
for (const audio of audios) {
if (audio.fileName === asset.p) {
// encoded is true
asset.e = 1;
asset.u = '';
asset.p = await audio.toDataURL();
}
}
}
}

Expand All @@ -325,9 +394,9 @@ export class DotLottieCommon {

let dataWithInlinedImages = this._animationsMap.get(animationId);

if (!dataWithInlinedImages) throw createError('Failed to find animation.');
if (!dataWithInlinedImages) throw new DotLottieError('Failed to find animation.');

dataWithInlinedImages = await this._findImageAssetAndInline(dataWithInlinedImages);
dataWithInlinedImages = await this._findAssetsAndInline(dataWithInlinedImages);

return dataWithInlinedImages;
}
Expand Down Expand Up @@ -365,6 +434,16 @@ export class DotLottieCommon {
return images;
}

public getAudio(): LottieAudioCommon[] {
const audio: LottieAudioCommon[] = [];

this.animations.map((animation) => {
return audio.push(...animation.audioAssets);
});

return audio;
}

public getTheme(themeId: string): LottieThemeCommon | undefined {
return this._themesMap.get(themeId);
}
Expand Down Expand Up @@ -431,6 +510,7 @@ export class DotLottieCommon {
if (this.animations.length > 1) {
// Rename assets incrementally if there are multiple animations
this._renameImageAssets();
this._renameAudioAssets();
}

const parallelPlugins = [];
Expand Down
1 change: 1 addition & 0 deletions packages/dotlottie-js/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './manifest';
export * from './dotlottie-theme-common';
export * from './dotlottie-state-machine-common';
export * from './dotlottie-state';
export * from './lottie-audio-common';

0 comments on commit fcbe995

Please sign in to comment.