Add media session integration (#646)

This commit is contained in:
Jordan Eldredge 2018-09-25 22:13:43 -07:00 committed by GitHub
parent b1674094db
commit 5f7948c011
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 195 additions and 32 deletions

View file

@ -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 });
}

View file

@ -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
}
}
];

View file

@ -4,6 +4,7 @@ export interface MediaTags {
tags: {
artist: string;
title: string;
album: string;
picture: {
data: number[];
type: string;

View file

@ -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
View 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();
});
}
}

View file

@ -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"
}

View file

@ -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
}
}

View file

@ -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,

View file

@ -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;

View file

@ -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));
});
}