Skip to content

Commit

Permalink
Update README and enhance audio player functionality with HLS support
Browse files Browse the repository at this point in the history
  • Loading branch information
StoneyEagle committed Jan 5, 2025
1 parent bd2a1cc commit 088cbe9
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 28 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# NoMercy MusicPlayer

**NoMercy MusicPlayer** is a headless HTML5 audio player with a queue system and equalizer. </br>
It is framework-agnostic and can be used with any JavaScript framework.
**NoMercy AudioPlayer** is a lightweight, customizable HTML5 audio player built with JavaScript. </br>
It is designed to support a variety of media formats and streaming protocols with a queue system. </br>
It is framework-agnostic and can be used with any JavaScript framework. </br>
Always feel like fighting player UI choices? This player has no UI components, you can build your own!

## Features

- **HTML5 Audio Support**: Compatible with popular media formats (MP3, FLAC, OGG).
- **HTML5 Audio Support**: Compatible with popular media formats (MP3, FLAC, M4A).
- **Streaming Support**: Handles streaming with HLS provided by [hls.js](https://github.com/video-dev/hls.js)
- **Framework Agnostic**: Works with any JavaScript framework.
- **No Ui**: No UI components, you can build your own.
- **Event-Driven**: Full event-driven API.
- **Queue system**: Add songs to a queue and play them in order, or shuffle them.
- **Equalizer**: Built-in equalizer.
- **Spectrum Analyzer**: Built-in spectrum analyzer.
- **Queue system**: Add songs to a queue, remove them, play them in order or shuffle them.
- **Equalizer**: Built-in equalizer with presets.
- **Spectrum Analyzer**: Built-in spectrum analyzer provided by [audiomotion.dev](https://github.com/hvianna/audioMotion-analyzer).

## Installation
```sh
Expand Down Expand Up @@ -225,8 +228,12 @@ const handleClick = (e: MouseEvent) => {
<template>
<button aria-label="Toggle Playback" @click="handleClick($event)">
<MoooomIcon icon="nmPause" v-if="isPlaying" className="size-9" />
<MoooomIcon icon="nmPlay" v-else className="size-9" />
<svg v-if="isPlaying" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5"></path>
</svg>
<svg v-else fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z"></path>
</svg>
</button>
</template>
Expand Down
15 changes: 11 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nomercy-entertainment/nomercy-music-player",
"version": "0.1.5",
"version": "0.1.6",
"description": "Full event-driven music player without a UI.",
"type": "commonjs",
"main": "./dist/index.js",
Expand Down Expand Up @@ -34,7 +34,8 @@
"dependencies": {
"@ionic/vue": "^8.4.1",
"@nomercy-entertainment/media-session": "^0.1.10",
"audiomotion-analyzer": "^4.5.0"
"audiomotion-analyzer": "^4.5.0",
"hls.js": "^1.5.13"
},
"devDependencies": {
"@types/jest": "^29.5.14",
Expand Down
49 changes: 45 additions & 4 deletions src/audioNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {isPlatform} from '@ionic/vue';
import Helpers from './helpers';

import type {AudioOptions, EQBand, BasePlaylistItem} from './types';
import HLS from 'hls.js';
import {PlayerState} from "./state";

import {spectrumAnalyser, AudioMotionAnalyzer, type ConstructorOptions} from "./spectrumAnalyzer";

export default class AudioNode<S extends BasePlaylistItem> {
_audioElement: HTMLAudioElement = <HTMLAudioElement>{};
public hls: HLS | undefined;
public state: PlayerState = PlayerState.STOPPED;
public duration: number = 0;
public currentTime: number = 0;
Expand All @@ -18,6 +20,8 @@ export default class AudioNode<S extends BasePlaylistItem> {
public context: AudioContext | null = null;
public motion: AudioMotionAnalyzer | null = null;

private accessToken?: string;

protected options: AudioOptions = <AudioOptions>{};
protected parent: Helpers<S>;
protected motionConfig: ConstructorOptions;
Expand Down Expand Up @@ -64,10 +68,47 @@ export default class AudioNode<S extends BasePlaylistItem> {
this._audioElement.remove();
}

public setSource(src: string) {
this._audioElement.src = src;
return this;
}
public setAccessToken(accessToken: string): void {
this.accessToken = accessToken;
}

public setSource(url: string): AudioNode<S> {
this._audioElement.pause();
this._audioElement.removeAttribute('src');

if (!url.endsWith('.m3u8')) {
this.hls?.destroy();
this.hls = undefined;

this._audioElement.src = `${url}${this.accessToken ? `?token=${this.accessToken}` : ''}`;
}
else if (HLS.isSupported()) {
this.hls ??= new HLS({
debug: false,
enableWorker: true,
lowLatencyMode: true,
maxBufferHole: 0,
maxBufferLength: 30,
maxBufferSize: 0,
autoStartLoad: true,
testBandwidth: true,

xhrSetup: (xhr) => {
if (this.accessToken) {
xhr.setRequestHeader('authorization', `Bearer ${this.accessToken}`);
}
},
});

this.hls?.loadSource(url);
this.hls?.attachMedia(this._audioElement);
}
else if (this._audioElement.canPlayType('application/vnd.apple.mpegurl')) {
this._audioElement.src = `${url}${this.accessToken ? `?token=${this.accessToken}` : ''}`;
}

return this;
}

public play(): Promise<void> {
return this._audioElement.play();
Expand Down
5 changes: 4 additions & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ export default class Helpers<S extends BasePlaylistItem> extends EventTarget {

public setAccessToken(accessToken: string): void {
this.accessToken = accessToken;

this._audioElement1.setAccessToken(accessToken);
this._audioElement2.setAccessToken(accessToken);
}

public setBaseUrl(baseUrl?: string): void {
Expand All @@ -157,7 +160,7 @@ export default class Helpers<S extends BasePlaylistItem> extends EventTarget {
return new Promise((resolve) => {
return resolve(
encodeURI(
`${this.baseUrl}${newItem?.path}${this.accessToken ? `?token=${this.accessToken}` : ''}`
`${this.baseUrl}${newItem?.path}`
).replace(/#/u, '%23')
);
}) as unknown as Promise<string>;
Expand Down
19 changes: 10 additions & 9 deletions src/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ export default class Queue<S extends BasePlaylistItem> extends Helpers<S> {

if (!payload) return;

this.getNewSource(payload).then((src) => {
this._currentAudio.setSource(src);
setTimeout(() => {
this._currentAudio.play().then();
}, 150);
this._currentAudio
.getAudioElement()
.setAttribute('data-src', payload?.id?.toString());
});
this.getNewSource(payload)
.then((src) => {
this._currentAudio.setSource(src);
this._currentAudio.play()
.then(() => {
this._currentAudio
.getAudioElement()
.setAttribute('data-src', payload?.id?.toString());
});
});
}

public next() {
Expand Down

0 comments on commit 088cbe9

Please sign in to comment.