Skip to content

Commit 40b093a

Browse files
authored
[core] async mediaCapabilities (#52)
* async mediaCapabilities * adding suggestions from @anton-karlovskiy * PR remarks * change useMediaCapabilitiesDecodingInfo api * remove unused code * fix tests / tweak readme * prevent unnecessary reruns of useEffect * tweak
1 parent 3c9f95a commit 40b093a

File tree

4 files changed

+92
-88
lines changed

4 files changed

+92
-88
lines changed

README.md

+12-16
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { useNetworkStatus } from 'react-adaptive-hooks/network';
2929
import { useSaveData } from 'react-adaptive-hooks/save-data';
3030
import { useHardwareConcurrency } from 'react-adaptive-hooks/hardware-concurrency';
3131
import { useMemoryStatus } from 'react-adaptive-hooks/memory';
32-
import { useMediaCapabilities } from 'react-adaptive-hooks/media-capabilities';
32+
import { useMediaCapabilitiesDecodingInfo } from 'react-adaptive-hooks/media-capabilities';
3333
```
3434

3535
and then use them in your components. Examples for each hook and utility can be found below:
@@ -160,16 +160,16 @@ const { deviceMemory } = useMemoryStatus(initialMemoryStatus);
160160

161161
### Media Capabilities
162162

163-
`useMediaCapabilities` utility for adapting based on the user's device media capabilities.
163+
`useMediaCapabilitiesDecodingInfo` utility for adapting based on the user's device media capabilities.
164164

165165
**Use case:** this hook can be used to check if we can play a certain content type. For example, Safari does not support WebM so we want to fallback to MP4 but if Safari at some point does support WebM it will automatically load WebM videos.
166166

167167
```js
168168
import React from 'react';
169169

170-
import { useMediaCapabilities } from 'react-adaptive-hooks/media-capabilities';
170+
import { useMediaCapabilitiesDecodingInfo } from 'react-adaptive-hooks/media-capabilities';
171171

172-
const webmMediaConfig = {
172+
const webmMediaDecodingConfig = {
173173
type: 'file', // 'record', 'transmission', or 'media-source'
174174
video: {
175175
contentType: 'video/webm;codecs=vp8', // valid content type
@@ -180,27 +180,22 @@ const webmMediaConfig = {
180180
}
181181
};
182182

183-
const initialMediaCapabilities = {showWarning: true};
183+
const initialMediaCapabilitiesInfo = { powerEfficient: true };
184184

185185
const MyComponent = ({ videoSources }) => {
186-
const { mediaCapabilities } = useMediaCapabilities(webmMediaConfig, initialMediaCapabilities);
186+
const { mediaCapabilitiesInfo } = useMediaCapabilitiesDecodingInfo(webmMediaDecodingConfig, initialMediaCapabilitiesInfo);
187187

188188
return (
189189
<div>
190-
<video src={mediaCapabilities.supported ? videoSources.webm : videoSources.mp4} controls>...</video>
191-
{ mediaCapabilities.showWarning && (
192-
<div class='muted'>
193-
Defaulted to mp4.
194-
Couldn't test webm support,
195-
either the media capabilities api is unavailable or no media configuration was given.
196-
</div>
197-
) }
190+
<video src={mediaCapabilitiesInfo.supported ? videoSources.webm : videoSources.mp4} controls>...</video>
198191
</div>
199192
);
200193
};
201194
```
202195

203-
This hook accepts a [media configuration](https://developer.mozilla.org/en-US/docs/Web/API/MediaConfiguration) object argument and an optional `initialMediaCapabilities` object argument, which can be used to provide a `mediaCapabilities` state value when the user's browser does not support the relevant [Media Capabilities API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capabilities_API) or no media configuration was given.
196+
`mediaCapabilitiesInfo` value contains the three Boolean properties supported, smooth, and powerEfficient, which describe whether decoding the media described would be supported, smooth, and powerEfficient.
197+
198+
This utility accepts a [MediaDecodingConfiguration](https://developer.mozilla.org/en-US/docs/Web/API/MediaDecodingConfiguration) object argument and an optional `initialMediaCapabilitiesInfo` object argument, which can be used to provide a `mediaCapabilitiesInfo` state value when the user's browser does not support the relevant [Media Capabilities API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capabilities_API) or no media configuration was given.
204199

205200
### Adaptive Code-loading & Code-splitting
206201

@@ -304,7 +299,8 @@ import {
304299
useNetworkStatus,
305300
useSaveData,
306301
useHardwareConcurrency,
307-
useMemoryStatus
302+
useMemoryStatus,
303+
useMediaCapabilitiesDecodingInfo
308304
} from 'react-adaptive-hooks/dist/index.umd.js';
309305
```
310306

index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ export { useNetworkStatus } from './network';
22
export { useSaveData } from './save-data';
33
export { useMemoryStatus } from './memory';
44
export { useHardwareConcurrency } from './hardware-concurrency';
5-
export { useMediaCapabilities } from './media-capabilities';
5+
export { useMediaCapabilitiesDecodingInfo } from './media-capabilities';

media-capabilities/index.js

+16-14
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,27 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import { useState, useEffect } from 'react';
1617

1718
const supported = typeof window !== 'undefined' && 'mediaCapabilities' in navigator;
1819

19-
const useMediaCapabilities = (mediaConfig, initialMediaCapabilities = {}) => {
20-
let mediaCapabilities = {
21-
supported,
22-
hasMediaConfig: !!mediaConfig
20+
const useMediaCapabilitiesDecodingInfo = (mediaDecodingConfig, initialMediaCapabilitiesInfo = {}) => {
21+
initialMediaCapabilitiesInfo = {
22+
...initialMediaCapabilitiesInfo
2323
};
2424

25-
mediaCapabilities = (mediaCapabilities.supported && mediaCapabilities.hasMediaConfig)
26-
? navigator.mediaCapabilities.decodingInfo(mediaConfig)
27-
: {
28-
...mediaCapabilities,
29-
...initialMediaCapabilities
30-
};
25+
const [mediaCapabilitiesInfo, setMediaCapabilitiesInfo] = useState(initialMediaCapabilitiesInfo);
3126

32-
return {mediaCapabilities};
33-
};
27+
useEffect(() => {
28+
supported &&
29+
navigator
30+
.mediaCapabilities
31+
.decodingInfo(mediaDecodingConfig)
32+
.then(setMediaCapabilitiesInfo)
33+
.catch(error => console.error(error));
34+
}, []);
3435

35-
export {
36-
useMediaCapabilities
36+
return { supported, mediaCapabilitiesInfo };
3737
};
38+
39+
export { useMediaCapabilitiesDecodingInfo };

media-capabilities/media-capabilities.test.js

+63-57
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import { renderHook } from '@testing-library/react-hooks';
1818

19-
const mediaConfig = {
19+
const mediaDecodingConfig = {
2020
type: 'file',
2121
audio: {
2222
contentType: 'audio/mp3',
@@ -34,73 +34,79 @@ const mediaCapabilitiesMapper = {
3434
}
3535
};
3636

37-
afterEach(function() {
38-
// Reload hook for every test
39-
jest.resetModules();
40-
});
37+
describe('useMediaCapabilitiesDecodingInfo', () => {
38+
test('should return supported flag on unsupported platforms', () => {
39+
jest.isolateModules(() => {
40+
const { useMediaCapabilitiesDecodingInfo } = require('.');
41+
const { result } = renderHook(() => useMediaCapabilitiesDecodingInfo(mediaDecodingConfig));
4142

42-
describe('useMediaCapabilities', () => {
43-
const navigator = window.navigator;
43+
expect(result.current.supported).toEqual(false);
44+
})
45+
});
4446

45-
afterEach(() => {
46-
if (!window.navigator) window.navigator = navigator;
47+
test('should return supported flag on unsupported platforms and no config given', () => {
48+
jest.isolateModules(() => {
49+
const { useMediaCapabilitiesDecodingInfo } = require('.');
50+
const { result } = renderHook(() => useMediaCapabilitiesDecodingInfo());
51+
52+
expect(result.current.supported).toEqual(false);
53+
})
4754
});
4855

49-
test('should return supported flag on unsupported platforms', () => {
50-
const { useMediaCapabilities } = require('./');
51-
const { result } = renderHook(() => useMediaCapabilities(mediaConfig));
56+
test('should return initialMediaCapabilitiesInfo for unsupported', () => {
57+
jest.isolateModules(() => {
58+
const initialMediaCapabilitiesInfo = {
59+
supported: true,
60+
smooth: false,
61+
powerEfficient: true
62+
};
5263

53-
expect(result.current.mediaCapabilities).toEqual({hasMediaConfig: true, supported: false});
54-
});
64+
const { useMediaCapabilitiesDecodingInfo } = require('.');
65+
const { result } = renderHook(() => useMediaCapabilitiesDecodingInfo(mediaDecodingConfig, initialMediaCapabilitiesInfo));
5566

56-
test('should return supported and hasMediaConfig flags on unsupported platforms and no config given', () => {
57-
const { useMediaCapabilities } = require('./');
58-
const { result } = renderHook(() => useMediaCapabilities());
59-
60-
expect(result.current.mediaCapabilities).toEqual({hasMediaConfig: false, supported: false});
67+
expect(result.current.mediaCapabilitiesInfo.supported).toBe(true);
68+
expect(result.current.mediaCapabilitiesInfo.smooth).toEqual(false);
69+
expect(result.current.mediaCapabilitiesInfo.powerEfficient).toEqual(true);
70+
});
6171
});
6272

63-
test('should return initialMediaCapabilities for unsupported', () => {
64-
const initialMediaCapabilities = {
65-
supported: true,
66-
smooth: false,
67-
powerEfficient: true
68-
};
69-
const { useMediaCapabilities } = require('./');
70-
const { result } = renderHook(() => useMediaCapabilities(mediaConfig, initialMediaCapabilities));
71-
72-
expect(result.current.mediaCapabilities.supported).toBe(true);
73-
expect(result.current.mediaCapabilities.smooth).toEqual(false);
74-
expect(result.current.mediaCapabilities.powerEfficient).toEqual(true);
75-
});
73+
test('should return supported flag when no config given', (done) => {
74+
jest.isolateModules(() => {
75+
global.navigator.mediaCapabilities = {
76+
decodingInfo: () => new Promise(resolve => resolve(true))
77+
};
7678

77-
test('should return hasMediaConfig flag when no config given', () => {
78-
Object.defineProperty(window.navigator, 'mediaCapabilities', {
79-
value: true,
80-
configurable: true,
81-
writable: true
79+
const { useMediaCapabilitiesDecodingInfo } = require('.');
80+
const { result, waitForNextUpdate } = renderHook(() => useMediaCapabilitiesDecodingInfo());
81+
82+
waitForNextUpdate()
83+
.then(() => {
84+
expect(result.current.supported).toEqual(true);
85+
86+
done();
87+
})
88+
.catch(err => done(err));
8289
});
83-
const { useMediaCapabilities } = require('./');
84-
const { result } = renderHook(() => useMediaCapabilities());
85-
86-
expect(result.current.mediaCapabilities).toEqual({hasMediaConfig: false, supported: true});
8790
});
88-
89-
test('should return MediaDecodingConfiguration for given media configuration', () => {
90-
Object.defineProperty(window.navigator, 'mediaCapabilities', {
91-
value: {
92-
decodingInfo: mediaConfig => mediaCapabilitiesMapper[mediaConfig.audio.contentType]
93-
},
94-
configurable: true,
95-
writable: true
96-
});
97-
const { useMediaCapabilities } = require('./');
98-
const { result } = renderHook(() => useMediaCapabilities(mediaConfig));
9991

100-
expect(result.current.mediaCapabilities).toEqual({
101-
powerEfficient: true,
102-
smooth: true,
103-
supported: true
92+
test('should return mediaCapabilitiesInfo for given media configuration', (done) => {
93+
jest.isolateModules(() => {
94+
global.navigator.mediaCapabilities = {
95+
decodingInfo: () => new Promise(resolve => resolve(mediaCapabilitiesMapper[mediaDecodingConfig.audio.contentType]))
96+
};
97+
98+
const { useMediaCapabilitiesDecodingInfo } = require('.');
99+
const { result, waitForNextUpdate } = renderHook(() => useMediaCapabilitiesDecodingInfo(mediaDecodingConfig));
100+
101+
waitForNextUpdate()
102+
.then(() => {
103+
expect(result.current.mediaCapabilitiesInfo.powerEfficient).toEqual(true);
104+
expect(result.current.mediaCapabilitiesInfo.smooth).toEqual(true);
105+
expect(result.current.mediaCapabilitiesInfo.supported).toEqual(true);
106+
107+
done();
108+
})
109+
.catch(err => done(err));
104110
});
105111
});
106-
});
112+
});

0 commit comments

Comments
 (0)