mirror of
https://github.com/generativefm/generative.fm.git
synced 2026-04-26 03:00:08 -04:00
Merge pull request #70 from generative-music/media-session-api
Media session api
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -8,5 +8,8 @@
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 7
|
||||
},
|
||||
"globals": {
|
||||
"MediaMetadata": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
50
src/store/middleware/media-session.middleware.js
Normal file
50
src/store/middleware/media-session.middleware.js
Normal 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;
|
||||
@@ -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: './',
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
21
src/store/middleware/silent-html5-audio.middleware/index.js
Normal file
21
src/store/middleware/silent-html5-audio.middleware/index.js
Normal 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;
|
||||
BIN
src/store/middleware/silent-html5-audio.middleware/silence.mp3
Normal file
BIN
src/store/middleware/silent-html5-audio.middleware/silence.mp3
Normal file
Binary file not shown.
@@ -62,6 +62,10 @@ const makeConfig = alias => ({
|
||||
use: path.resolve('./piece-loader.js'),
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.mp3$/,
|
||||
use: 'file-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user