mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-19 09:33:47 +00:00
Add media session integration (#646)
This commit is contained in:
parent
b1674094db
commit
5f7948c011
10 changed files with 195 additions and 32 deletions
|
|
@ -262,8 +262,8 @@ export function loadMediaFile(
|
|||
}
|
||||
|
||||
if (metaData != null) {
|
||||
const { artist, title } = metaData;
|
||||
dispatch({ type: SET_MEDIA_TAGS, artist, title, id });
|
||||
const { artist, title, album } = metaData;
|
||||
dispatch({ type: SET_MEDIA_TAGS, artist, title, album, id });
|
||||
} else if ("blob" in track) {
|
||||
// Blobs can be loaded quickly
|
||||
dispatch(fetchMediaTags(track.blob, id));
|
||||
|
|
@ -295,14 +295,14 @@ export function fetchMediaTags(file: string | Blob, id: number): Dispatchable {
|
|||
const data = await genMediaTags(file, await requireJSMediaTags());
|
||||
// There's more data here, but we don't have a use for it yet:
|
||||
// https://github.com/aadsm/jsmediatags#shortcuts
|
||||
const { artist, title, picture } = data.tags;
|
||||
const { artist, title, album, picture } = data.tags;
|
||||
let albumArtUrl = null;
|
||||
if (picture) {
|
||||
const byteArray = new Uint8Array(picture.data);
|
||||
const blob = new Blob([byteArray], { type: picture.type });
|
||||
albumArtUrl = URL.createObjectURL(blob);
|
||||
}
|
||||
dispatch({ type: SET_MEDIA_TAGS, artist, title, albumArtUrl, id });
|
||||
dispatch({ type: SET_MEDIA_TAGS, artist, title, album, albumArtUrl, id });
|
||||
} catch (e) {
|
||||
dispatch({ type: MEDIA_TAG_REQUEST_FAILED, id });
|
||||
}
|
||||
|
|
|
|||
40
js/config.ts
40
js/config.ts
|
|
@ -27,6 +27,10 @@ if (config.audioUrl && !config.initialTracks) {
|
|||
}
|
||||
|
||||
export const skinUrl = config.skinUrl === undefined ? null : config.skinUrl;
|
||||
|
||||
// https://freemusicarchive.org/music/netBloc_Artists/netBloc_Vol_24_tiuqottigeloot/
|
||||
const album = "netBloc Vol. 24: tiuqottigeloot";
|
||||
|
||||
export const initialTracks = config.initialTracks || [
|
||||
{
|
||||
metaData: { artist: "DJ Mike Llama", title: "Llama Whippin' Intro" },
|
||||
|
|
@ -39,7 +43,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 322.612245,
|
||||
metaData: {
|
||||
title: "Heroines",
|
||||
artist: "Diablo Swing Orchestra"
|
||||
artist: "Diablo Swing Orchestra",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -48,7 +53,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 190.093061,
|
||||
metaData: {
|
||||
title: "We Are Going To Eclecfunk Your Ass",
|
||||
artist: "Eclectek"
|
||||
artist: "Eclectek",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -57,7 +63,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 214.622041,
|
||||
metaData: {
|
||||
title: "Seventeen",
|
||||
artist: "Auto-Pilot"
|
||||
artist: "Auto-Pilot",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -66,7 +73,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 181.838367,
|
||||
metaData: {
|
||||
title: "Microphone",
|
||||
artist: "Muha"
|
||||
artist: "Muha",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +83,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 86.047347,
|
||||
metaData: {
|
||||
title: "Stumble",
|
||||
artist: "Just Plain Ant"
|
||||
artist: "Just Plain Ant",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -84,7 +93,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 226.795102,
|
||||
metaData: {
|
||||
title: "God Damn",
|
||||
artist: "Sleaze"
|
||||
artist: "Sleaze",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -93,7 +103,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 207.072653,
|
||||
metaData: {
|
||||
title: "Hola Hola Bossa Nova",
|
||||
artist: "Juanitos"
|
||||
artist: "Juanitos",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -102,7 +113,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 314.331429,
|
||||
metaData: {
|
||||
title: "Resolutions (Chris Summer Remix)",
|
||||
artist: "Entertainment for the Braindead"
|
||||
artist: "Entertainment for the Braindead",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -111,7 +123,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 204.042449,
|
||||
metaData: {
|
||||
title: "Trail",
|
||||
artist: "Nobara Hayakawa"
|
||||
artist: "Nobara Hayakawa",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -120,7 +133,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 201.116735,
|
||||
metaData: {
|
||||
title: "Tongue Tied",
|
||||
artist: "Paper Navy"
|
||||
artist: "Paper Navy",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -129,7 +143,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 245.394286,
|
||||
metaData: {
|
||||
title: "Garage",
|
||||
artist: "60 Tigres"
|
||||
artist: "60 Tigres",
|
||||
album
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -138,7 +153,8 @@ export const initialTracks = config.initialTracks || [
|
|||
duration: 221.44,
|
||||
metaData: {
|
||||
title: "The Cycle (Featuring Mista Mista)",
|
||||
artist: "CM aka Creative"
|
||||
artist: "CM aka Creative",
|
||||
album
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export interface MediaTags {
|
|||
tags: {
|
||||
artist: string;
|
||||
title: string;
|
||||
album: string;
|
||||
picture: {
|
||||
data: number[];
|
||||
type: string;
|
||||
|
|
|
|||
12
js/index.js
12
js/index.js
|
|
@ -13,6 +13,7 @@ import green from "../skins/Green-Dimension-V2.wsz";
|
|||
import MilkdropWindow from "./components/MilkdropWindow";
|
||||
import screenshotInitialState from "./screenshotInitialState";
|
||||
import WebampLazy from "./webampLazy";
|
||||
import enableMediaSession from "./mediaSession";
|
||||
import {
|
||||
STEP_MARQUEE,
|
||||
UPDATE_TIME_ELAPSED,
|
||||
|
|
@ -66,6 +67,8 @@ const requireJSMediaTags = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const DEFAULT_DOCUMENT_TITLE = document.title;
|
||||
|
||||
const NOISY_ACTION_TYPES = new Set([
|
||||
STEP_MARQUEE,
|
||||
UPDATE_TIME_ELAPSED,
|
||||
|
|
@ -268,6 +271,15 @@ Raven.context(() => {
|
|||
}
|
||||
});
|
||||
|
||||
webamp.__onTrackDidChange(track => {
|
||||
document.title =
|
||||
track == null
|
||||
? DEFAULT_DOCUMENT_TITLE
|
||||
: `${track.metaData.title} - ${track.metaData.artist}`;
|
||||
});
|
||||
|
||||
enableMediaSession(webamp);
|
||||
|
||||
webamp.renderWhenReady(document.getElementById("app"));
|
||||
|
||||
// Expose webamp instance for debugging and integration tests.
|
||||
|
|
|
|||
41
js/mediaSession.js
Normal file
41
js/mediaSession.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
export default function enableMediaSession(webamp) {
|
||||
if ("mediaSession" in navigator) {
|
||||
/* global MediaMetadata */
|
||||
webamp.__onTrackDidChange(({ title, artist, album, albumArtUrl }) => {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
artwork: albumArtUrl
|
||||
? [
|
||||
{
|
||||
src: albumArtUrl
|
||||
// We don't currently know these.
|
||||
// sizes: "96x96",
|
||||
// type: "image/png"
|
||||
}
|
||||
]
|
||||
: []
|
||||
});
|
||||
});
|
||||
|
||||
navigator.mediaSession.setActionHandler("play", () => {
|
||||
webamp.play();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("pause", () => {
|
||||
webamp.pause();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("seekbackward", () => {
|
||||
webamp.seekBackward(10);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("seekforward", () => {
|
||||
webamp.seekForward(10);
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("previoustrack", () => {
|
||||
webamp.previousTrack();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("nexttrack", () => {
|
||||
webamp.nextTrack();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ describe("playlist reducer", () => {
|
|||
selected: false,
|
||||
duration: null,
|
||||
defaultName: "My Track Name",
|
||||
mediaTagsRequestStatus: "NOT_REQUESTED",
|
||||
mediaTagsRequestStatus: "INITIALIZED",
|
||||
url: "url://some-url"
|
||||
}
|
||||
},
|
||||
|
|
@ -57,7 +57,7 @@ describe("playlist reducer", () => {
|
|||
id: 100,
|
||||
selected: false,
|
||||
duration: null,
|
||||
mediaTagsRequestStatus: "NOT_REQUESTED",
|
||||
mediaTagsRequestStatus: "INITIALIZED",
|
||||
defaultName: "My Track Name",
|
||||
url: "url://some-url"
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ describe("playlist reducer", () => {
|
|||
id: 100,
|
||||
selected: false,
|
||||
duration: null,
|
||||
mediaTagsRequestStatus: "NOT_REQUESTED",
|
||||
mediaTagsRequestStatus: "INITIALIZED",
|
||||
defaultName: "My Track Name",
|
||||
url: "url://some-url"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ const playlist = (
|
|||
defaultName: action.defaultName || null,
|
||||
duration: action.duration == null ? null : action.duration,
|
||||
url: action.url,
|
||||
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.NOT_REQUESTED
|
||||
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.INITIALIZED
|
||||
}
|
||||
},
|
||||
// TODO: This could probably be made to work, but we clear it just to be safe.
|
||||
|
|
@ -212,6 +212,7 @@ const playlist = (
|
|||
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.COMPLETE,
|
||||
title: action.title,
|
||||
artist: action.artist,
|
||||
album: action.album,
|
||||
albumArtUrl: action.albumArtUrl
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import {
|
|||
PlaylistTrack,
|
||||
WebampWindow,
|
||||
WindowId,
|
||||
WindowInfo
|
||||
WindowInfo,
|
||||
LoadedURLTrack
|
||||
} from "./types";
|
||||
import { createSelector } from "reselect";
|
||||
import {
|
||||
|
|
@ -18,7 +19,9 @@ import {
|
|||
TRACK_HEIGHT,
|
||||
WINDOW_RESIZE_SEGMENT_WIDTH,
|
||||
WINDOW_RESIZE_SEGMENT_HEIGHT,
|
||||
WINDOW_WIDTH
|
||||
WINDOW_WIDTH,
|
||||
MEDIA_STATUS,
|
||||
MEDIA_TAG_REQUEST_STATUS
|
||||
} from "./constants";
|
||||
import { createPlaylistURL } from "./playlistHtml";
|
||||
import * as fromPlaylist from "./reducers/playlist";
|
||||
|
|
@ -227,6 +230,50 @@ export const getCurrentTrackDisplayName = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getMediaIsPlaying = (state: AppState) =>
|
||||
state.media.status === MEDIA_STATUS.PLAYING;
|
||||
|
||||
export const getCurrentTrack = createSelector(
|
||||
getCurrentTrackId,
|
||||
getPlaylist,
|
||||
(trackId, playlist): PlaylistTrack | null => {
|
||||
return trackId == null ? null : playlist.tracks[trackId];
|
||||
}
|
||||
);
|
||||
export const getCurrentlyPlayingTrackIdIfLoaded = createSelector(
|
||||
getMediaIsPlaying,
|
||||
getCurrentTrack,
|
||||
(mediaIsPlaying, currentTrack) => {
|
||||
if (
|
||||
!mediaIsPlaying ||
|
||||
!currentTrack ||
|
||||
currentTrack.mediaTagsRequestStatus ===
|
||||
MEDIA_TAG_REQUEST_STATUS.INITIALIZED
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return currentTrack.id;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCurrentTrackInfo = createSelector(
|
||||
getCurrentTrack,
|
||||
(track: PlaylistTrack | null): LoadedURLTrack | null => {
|
||||
if (track == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
url: track.url,
|
||||
metaData: {
|
||||
title: track.title || null,
|
||||
artist: track.artist || null,
|
||||
album: track.album || null,
|
||||
albumArtUrl: track.albumArtUrl || null
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const getMinimalMediaText = createSelector(
|
||||
getCurrentTrackNumber,
|
||||
getCurrentTrackDisplayName,
|
||||
|
|
|
|||
14
js/types.ts
14
js/types.ts
|
|
@ -297,6 +297,7 @@ export type Action =
|
|||
id: number;
|
||||
title: string;
|
||||
artist: string;
|
||||
album?: string;
|
||||
albumArtUrl?: string | null;
|
||||
}
|
||||
| {
|
||||
|
|
@ -394,6 +395,8 @@ interface TrackInfo {
|
|||
metaData?: {
|
||||
artist: string;
|
||||
title: string;
|
||||
album?: string;
|
||||
albumArtUrl?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -422,6 +425,16 @@ export interface BlobTrack extends TrackInfo {
|
|||
blob: Blob;
|
||||
}
|
||||
|
||||
export interface LoadedURLTrack {
|
||||
url: string;
|
||||
metaData: {
|
||||
artist: string | null;
|
||||
title: string | null;
|
||||
album: string | null;
|
||||
albumArtUrl: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Many methods on the webamp instance deal with track.
|
||||
*
|
||||
|
|
@ -433,6 +446,7 @@ export interface PlaylistTrack {
|
|||
id: number;
|
||||
artist?: string;
|
||||
title?: string;
|
||||
album?: string;
|
||||
url: string;
|
||||
defaultName: string | null;
|
||||
albumArtUrl?: string | null;
|
||||
|
|
|
|||
|
|
@ -6,12 +6,18 @@ import getStore from "./store";
|
|||
import App from "./components/App";
|
||||
import Hotkeys from "./hotkeys";
|
||||
import Media from "./media";
|
||||
import { getTrackCount, getTracks } from "./selectors";
|
||||
import * as Selectors from "./selectors";
|
||||
import {
|
||||
setSkinFromUrl,
|
||||
loadMediaFiles,
|
||||
setWindowSize,
|
||||
loadFilesFromReferences
|
||||
loadFilesFromReferences,
|
||||
play,
|
||||
pause,
|
||||
seekBackward,
|
||||
seekForward,
|
||||
next,
|
||||
previous
|
||||
} from "./actionCreators";
|
||||
import { LOAD_STYLE } from "./constants";
|
||||
import { uniqueId, objectMap, objectForEach } from "./utils";
|
||||
|
|
@ -27,7 +33,6 @@ import {
|
|||
LOADED,
|
||||
REGISTER_VISUALIZER,
|
||||
SET_Z_INDEX,
|
||||
SET_MEDIA,
|
||||
CLOSE_REQUESTED
|
||||
} from "./actionTypes";
|
||||
import Emitter from "./emitter";
|
||||
|
|
@ -172,9 +177,33 @@ class Winamp {
|
|||
}
|
||||
}
|
||||
|
||||
play() {
|
||||
this.store.dispatch(play());
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.store.dispatch(pause());
|
||||
}
|
||||
|
||||
seekBackward(seconds) {
|
||||
this.store.dispatch(seekBackward(seconds));
|
||||
}
|
||||
|
||||
seekForward(seconds) {
|
||||
this.store.dispatch(seekForward(seconds));
|
||||
}
|
||||
|
||||
nextTrack() {
|
||||
this.store.dispatch(next());
|
||||
}
|
||||
|
||||
previousTrack() {
|
||||
this.store.dispatch(previous());
|
||||
}
|
||||
|
||||
// Append this array of tracks to the end of the current playlist.
|
||||
appendTracks(tracks) {
|
||||
const nextIndex = getTrackCount(this.store.getState());
|
||||
const nextIndex = Selectors.getTrackCount(this.store.getState());
|
||||
this.store.dispatch(loadMediaFiles(tracks, LOAD_STYLE.BUFFER, nextIndex));
|
||||
}
|
||||
|
||||
|
|
@ -193,14 +222,16 @@ class Winamp {
|
|||
return this._actionEmitter.on(CLOSE_WINAMP, cb);
|
||||
}
|
||||
|
||||
onTrackDidChange(cb) {
|
||||
return this._actionEmitter.on(SET_MEDIA, action => {
|
||||
const tracks = getTracks(this.store.getState());
|
||||
const track = tracks[action.id];
|
||||
if (track == null) {
|
||||
__onTrackDidChange(cb) {
|
||||
let previousTrackId = null;
|
||||
this.store.subscribe(() => {
|
||||
const state = this.store.getState();
|
||||
const trackId = Selectors.getCurrentlyPlayingTrackIdIfLoaded(state);
|
||||
if (trackId === previousTrackId) {
|
||||
return;
|
||||
}
|
||||
cb({ url: track.url });
|
||||
previousTrackId = trackId;
|
||||
cb(trackId == null ? null : Selectors.getCurrentTrackInfo(state));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue