Skip to content

Commit e4e042a

Browse files
Add Layer selection
1 parent c2c577e commit e4e042a

File tree

15 files changed

+284
-21
lines changed

15 files changed

+284
-21
lines changed

src/components/BottomBar/BottomBar.style.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const styles = AppStyleSheet.create({
77
alignItems: 'stretch',
88
justifyContent: 'space-between',
99
paddingHorizontal: 20,
10+
width: '100%',
1011
},
1112
});
1213
export default styles;

src/components/BottomBar/BottomBar.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { View, TouchableOpacity, Platform } from 'react-native';
44

55
import styles from './BottomBar.style';
66

7-
export const BottomBar = ({ displayStatsInformation, focus }) => {
7+
export const BottomBar = ({ displayStatsInformation, displaySimulcastSelection, focus }) => {
88
const iconSize = Platform.OS === 'android' && Platform.isTV ? 's' : 'm';
99
return (
1010
<View style={styles.wrapper}>
@@ -17,6 +17,15 @@ export const BottomBar = ({ displayStatsInformation, focus }) => {
1717
>
1818
<Icon testID="infoIcon" name="info" size={iconSize} />
1919
</TouchableOpacity>
20+
<TouchableOpacity
21+
testID="settingsIconButton"
22+
hasTVPreferredFocus={focus}
23+
onPress={() => {
24+
displaySimulcastSelection();
25+
}}
26+
>
27+
<Icon testID="settingsIcon" name="settings" size={iconSize} />
28+
</TouchableOpacity>
2029
</View>
2130
);
2231
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { AppStyleSheet as StyleSheet } from '@dolbyio/uikit-react-native';
2+
import { Platform } from 'react-native';
3+
4+
const styles = StyleSheet.create({
5+
outerContainer: {
6+
flex: 1,
7+
backgroundColor: '#292930E5',
8+
marginTop: 100,
9+
borderTopLeftRadius: 14,
10+
borderTopRightRadius: 14,
11+
},
12+
simulcastCell: {
13+
width: '100%',
14+
height: 50,
15+
flexDirection: 'row',
16+
backgroundColor: '#34343B',
17+
marginVertical: 5,
18+
justifyContent: 'center',
19+
alignContent: 'center',
20+
},
21+
outerContainerTV: {
22+
height: '50%',
23+
width: '20%',
24+
position: 'absolute',
25+
backgroundColor: '#292930',
26+
bottom: 20,
27+
left: '80%',
28+
borderRadius: 14,
29+
},
30+
innerContainer: {
31+
marginTop: 10,
32+
flexDirection: 'column',
33+
alignItems: 'stretch',
34+
justifyContent: 'space-between',
35+
paddingHorizontal: 0,
36+
},
37+
simulcastOptionsContainer: {
38+
marginTop: Platform.isTV ? 10 : 60,
39+
marginHorizontal: 16,
40+
},
41+
closeIcon: {
42+
flexDirection: 'row',
43+
justifyContent: 'flex-end',
44+
marginTop: 20,
45+
paddingHorizontal: 20,
46+
},
47+
});
48+
export default styles;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import { View, TouchableOpacity, FlatList, Platform } from 'react-native';
3+
4+
import Icon from '../../uikit/components/Icon/Icon';
5+
import Text from '../text/Text';
6+
7+
import styles from './SimulcastView.style';
8+
9+
export const SimulcastView = ({ streamQualityList, selectedStreamQuality, onClose, onSelectStreamQuality }) => {
10+
const isSelected = (streamQuality) => {
11+
return selectedStreamQuality === streamQuality;
12+
};
13+
const renderItem = ({ item }) => (
14+
<TouchableOpacity
15+
onPress={() => {
16+
onSelectStreamQuality(item);
17+
}}
18+
>
19+
<View style={styles.simulcastCell}>
20+
{isSelected(item.streamQuality) && <Icon testID="checkmarkIcon" name="checkmark" size="xs" />}
21+
<Text style={{ alignSelf: 'center', height: '100%' }} type="bodyDefault">
22+
{item.streamQuality}
23+
</Text>
24+
</View>
25+
</TouchableOpacity>
26+
);
27+
28+
return (
29+
<View style={Platform.isTV ? styles.outerContainerTV : styles.outerContainer}>
30+
<View style={styles.closeIcon}>
31+
<TouchableOpacity testID="closeIconButton" hasTVPreferredFocus onPress={onClose}>
32+
<Icon testID="closeIcon" name="close" size="s" />
33+
</TouchableOpacity>
34+
</View>
35+
<View style={styles.innerContainer}>
36+
<Text
37+
testID="simulcastTitle"
38+
id="simulcastTitle"
39+
type="h2"
40+
align={Platform.isTV ? 'left' : 'center'}
41+
style={{ paddingTop: 16, paddingLeft: 16 }}
42+
/>
43+
<View style={styles.simulcastOptionsContainer}>
44+
<FlatList
45+
testID="simulcastList"
46+
data={streamQualityList}
47+
keyExtractor={(item) => item.streamQuality}
48+
renderItem={renderItem}
49+
/>
50+
</View>
51+
</View>
52+
</View>
53+
);
54+
};

src/components/SimulcastView/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { SimulcastView } from './SimulcastView';
2+
3+
export default SimulcastView;

src/components/StreamStats/StreamStats.style.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const styles = StyleSheet.create({
1616
bottom: 20,
1717
left: 20,
1818
borderRadius: 14,
19+
position: 'absolute',
1920
},
2021
innerContainer: {
2122
marginTop: 10,

src/screens/multiview/MultiView.tsx

+32-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
MediaTrackInfo,
66
ViewProjectSourceMapping,
77
MediaStreamSource,
8+
MediaStreamLayers,
9+
MediaLayer,
10+
LayerInfo,
811
} from '@millicast/sdk';
912
import { useNetInfo } from '@react-native-community/netinfo';
1013
import React, { useEffect, useRef, useState } from 'react';
@@ -25,7 +28,7 @@ import {
2528
import { RTCView } from 'react-native-webrtc';
2629
import { useSelector, useDispatch } from 'react-redux';
2730

28-
import { RemoteTrackSource } from '../../types/RemoteTrackSource.types';
31+
import { RemoteTrackSource, SimulcastQuality } from '../../types/RemoteTrackSource.types';
2932
import { Routes } from '../../types/routes.types';
3033

3134
window.Logger = MillicastLogger;
@@ -42,6 +45,7 @@ export const MultiView = ({ navigation }) => {
4245
const playing = useSelector((state) => state.viewerReducer.playing);
4346
const error = useSelector((state) => state.viewerReducer.error);
4447
const audioRemoteTrackSource = useSelector((state) => state.viewerReducer.audioRemoteTrackSource);
48+
4549
const dispatch = useDispatch();
4650
const { routes, index } = navigation.getState();
4751
const currentRoute = routes[index].name;
@@ -51,6 +55,7 @@ export const MultiView = ({ navigation }) => {
5155
const sourceIdsRef = useRef([]);
5256
const netInfo = useNetInfo();
5357
const audioRemoteTrackSourceRef = useRef(null);
58+
5459
const [isReconnectionScheduled, setIsReconnectionScheduled] = useState<boolean>(false);
5560

5661
remoteTrackSourcesRef.current = remoteTrackSources;
@@ -144,6 +149,25 @@ export const MultiView = ({ navigation }) => {
144149
}
145150
};
146151

152+
const buildQualityOptions = (active, layers) => {
153+
const descendingLayers = active.sort((a, b) => b.height - a.height);
154+
155+
const qualityOptions: SimulcastQuality[] = descendingLayers.map((active) => ({
156+
simulcastLayer: {
157+
bitrate: active.bitrate,
158+
encodingId: active.id,
159+
simulcastIdx: active.simulcastIdx,
160+
spatialLayerId: layers.find((layer) => layer.simulcastIdx === active.simulcastIdx)?.spatialLayerId,
161+
temporalLayerId: layers.find((layer) => layer.simulcastIdx === active.simulcastIdx)?.temporalLayerId,
162+
maxSpatialLayerId: layers.find((layer) => layer.simulcastIdx === active.simulcastIdx)?.maxSpatialLayerId,
163+
maxTemporalLayerId: layers.find((layer) => layer.simulcastIdx === active.simulcastIdx)?.maxTemporalLayerId,
164+
},
165+
streamQuality: `${active.height}p`,
166+
}));
167+
168+
return [{ streamQuality: 'Auto' } as SimulcastQuality, ...qualityOptions];
169+
};
170+
147171
const subscribe = async () => {
148172
if (millicastViewRef.current?.isActive()) {
149173
return;
@@ -193,6 +217,7 @@ export const MultiView = ({ navigation }) => {
193217
payload: newRemoteTrackSource,
194218
});
195219
}
220+
196221
await viewer.project(sourceId, mappingForProjection);
197222
dispatch({
198223
type: 'viewer/addRemoteTrackSource',
@@ -231,11 +256,15 @@ export const MultiView = ({ navigation }) => {
231256
// A new source was multiplexed over the vad tracks
232257
break;
233258
case 'layers':
259+
const { medias } = data;
260+
const mediaId = Object.keys(medias)[0];
261+
const { active, layers } = (data as MediaStreamLayers).medias[mediaId] ?? {};
262+
const streamQualities = buildQualityOptions(active, layers);
263+
234264
dispatch({
235265
type: 'viewer/setActiveLayers',
236-
payload: data.medias?.['0']?.active,
266+
payload: { mediaId, streamQualities },
237267
});
238-
// Updated layer information for each simulcast/svc video track
239268
break;
240269
default:
241270
console.log('Unknown event', name);

src/screens/singleStreamView/SingleStreamView.style.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const styles = () =>
1313
...StyleSheet.absoluteFill,
1414
},
1515
bottomMultimediaContainer: {
16-
width,
16+
width: '100%',
1717
alignItems: 'stretch',
1818
justifyContent: 'space-between',
1919
paddingVertical: 15,

0 commit comments

Comments
 (0)