Merge pull request #70 from generative-music/media-session-api

Media session api
This commit is contained in:
Alex Bainter
2019-03-29 22:35:03 -05:00
committed by GitHub
9 changed files with 96 additions and 20 deletions

View File

@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Implement the [Media Session API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Session_API) which adds better control integration for Android Chrome users (including bluetooth)
## [0.11.0] - 2019-03-24
### Fixed
- Quickly switching between pieces while music is playing should no longer allow multiple pieces to play simultaneously
@@ -179,7 +185,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Sound files will no longer be fetched and cached during service worker installation. They'll be cached once they are fetched for the first time. This significantly reduces cache usage since only one audio format is used per client.
[unreleased]: https://github.com/generative-music/generative.fm/compare/v0.10.1...HEAD
[unreleased]: https://github.com/generative-music/generative.fm/compare/v0.11.0...HEAD
[0.11.0]: https://github.com/generative-music/generative.fm/compare/v0.10.1...v0.11.0
[0.10.1]: https://github.com/generative-music/generative.fm/compare/v0.9.0...v0.10.1
[0.10.0]: https://github.com/generative-music/generative.fm/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/generative-music/generative.fm/compare/v0.8.1...v0.9.0

View File

@@ -8,5 +8,8 @@
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 7
},
"globals": {
"MediaMetadata": true
}
}

View File

@@ -4,6 +4,8 @@ import rootReducer from './reducers/root.reducer';
import piecesMiddleware from './middleware/pieces.middleware';
import localStorageMiddleware from './middleware/local-storage.middleware';
import beforeUnloadMiddleware from './middleware/before-unload.middleware';
import silentHtml5AudioMiddleware from './middleware/silent-html5-audio.middleware';
import mediaSessionMiddleware from './middleware/media-session.middleware';
import STATE_STORAGE_KEY from './middleware/local-storage.middleware/key';
import getOnlineStatus from './get-online-status';
import pieces from '../pieces/index';
@@ -32,7 +34,12 @@ if (isMobile) {
initialState.isMuted = false;
}
const allMiddlewares = [piecesMiddleware, localStorageMiddleware];
const allMiddlewares = [
piecesMiddleware,
silentHtml5AudioMiddleware,
mediaSessionMiddleware,
localStorageMiddleware,
];
const desktopMiddlewares = allMiddlewares.concat([beforeUnloadMiddleware]);
const store = createStore(

View File

@@ -0,0 +1,50 @@
import pieces from '@pieces';
import artists from '@data/artists';
import play from '../actions/creators/play.creator';
import stop from '../actions/creators/stop.creator';
import next from '../actions/creators/next.creator';
import previous from '../actions/creators/previous.creator';
import SELECT_PIECE from '../actions/types/select-piece.type';
const mediaSessionActionReduxCreatorPairs = [
['play', play],
['pause', stop],
['nexttrack', next],
['previoustrack', previous],
];
const updateMetadata = selectedPieceId => {
const selectedPiece = pieces.find(({ id }) => id === selectedPieceId);
if (typeof selectedPiece !== 'undefined') {
const { title, artist, image } = selectedPiece;
navigator.mediaSession.metadata = new MediaMetadata({
title,
artist: artists[artist],
album: 'Generative.fm',
artwork: [{ src: image, type: 'image/png' }],
});
}
};
const mediaSessionMiddleware = store => nextMiddleware => {
if (navigator.mediaSession) {
mediaSessionActionReduxCreatorPairs.forEach(
([mediaSessionAction, reduxActionCreator]) => {
navigator.mediaSession.setActionHandler(mediaSessionAction, () => {
store.dispatch(reduxActionCreator());
});
}
);
const { selectedPieceId } = store.getState();
updateMetadata(selectedPieceId);
return action => {
if (action.type === SELECT_PIECE) {
updateMetadata(action.payload);
}
return nextMiddleware(action);
};
}
return action => nextMiddleware(action);
};
export default mediaSessionMiddleware;

View File

@@ -10,7 +10,8 @@ let isPerformanceBuilding = false;
let queuedPiece = null;
const sampleSource =
location.hostname === 'localhost'
location.hostname !== 'generative.fm' &&
location.hostname !== 'staging.generative.fm'
? {
baseUrl: './',
}

View File

@@ -1,9 +1,5 @@
import { context } from 'tone';
const SILENT_SOUND_URL =
'data:audio/mp3;base64,//MkxAAHiAICWABElBeKPL/RANb2w+yiT1g/gTok//lP/W/l3h8QO/OCdCqCW2Cw//MkxAQHkAIWUAhEmAQXWUOFW2dxPu//9mr60ElY5sseQ+xxesmHKtZr7bsqqX2L//MkxAgFwAYiQAhEAC2hq22d3///9FTV6tA36JdgBJoOGgc+7qvqej5Zu7/7uI9l//MkxBQHAAYi8AhEAO193vt9KGOq+6qcT7hhfN5FTInmwk8RkqKImTM55pRQHQSq//MkxBsGkgoIAABHhTACIJLf99nVI///yuW1uBqWfEu7CgNPWGpUadBmZ////4sL//MkxCMHMAH9iABEmAsKioqKigsLCwtVTEFNRTMuOTkuNVVVVVVVVVVVVVVVVVVV//MkxCkECAUYCAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV';
let shouldPlaySilence = true;
// "inspired by" (read: ripped off) https://github.com/tambien/StartAudioContext/blob/master/StartAudioContext.js
const startAudioContext = () => {
if (context.state && context.state !== 'running') {
@@ -18,19 +14,6 @@ const startAudioContext = () => {
if (context.resume) {
context.resume();
}
if (shouldPlaySilence) {
shouldPlaySilence = false;
// Play a silent sound via an HTML audio element to enable proper audio playback in iOS
const audio = document.createElement('audio');
if (audio.canPlayType('audio/mp3') !== '') {
audio.controls = false;
audio.preload = 'auto';
audio.loop = false;
audio.src = SILENT_SOUND_URL;
audio.play();
}
}
}
};

View File

@@ -0,0 +1,21 @@
import PLAY from '../../actions/types/play.type';
import STOP from '../../actions/types/stop.type';
import silentAudioFile from './silence.mp3';
// Plays a silent audio file from an HTML5 audio element when music is playing.
// This is required to get both iOS devices and the media session API to behave.
const silentHtml5AudioMiddleware = (/*store*/) => next => {
const audio = document.createElement('audio');
audio.src = silentAudioFile;
audio.loop = true;
return action => {
if (action.type === PLAY) {
audio.play();
} else if (action.type === STOP) {
audio.pause();
}
return next(action);
};
};
export default silentHtml5AudioMiddleware;

View File

@@ -62,6 +62,10 @@ const makeConfig = alias => ({
use: path.resolve('./piece-loader.js'),
type: 'javascript/auto',
},
{
test: /\.mp3$/,
use: 'file-loader',
},
],
},
plugins: [