Skip to content

Commit a418b0b

Browse files
authored
Merge pull request #1598 from StoDevX/fix-ksto
Fix KSTO
2 parents 3ecb566 + 8a05edf commit a418b0b

File tree

4 files changed

+128
-56
lines changed

4 files changed

+128
-56
lines changed

package-lock.json

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"react-native-sortable-list": "github:hawkrives/react-native-sortable-list#7b72cc8e16cc71751836b49b3ea4bbc9e8e9cb99",
8484
"react-native-tableview-simple": "0.16.11",
8585
"react-native-vector-icons": "4.4.0",
86-
"react-native-video": "2.0.0",
86+
"react-native-video": "github:drewvolz/react-native-video#f73b7a04843fb5b17fb4fbedbe593c225594ab3b",
8787
"react-navigation": "1.0.0-beta.11",
8888
"react-redux": "5.0.6",
8989
"redux": "3.7.2",

source/lib/promise-timeout.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @flow
2+
3+
export function promiseTimeout(
4+
ms: number,
5+
promise: Promise<any>,
6+
): Promise<any> {
7+
return new Promise(function(resolve, reject) {
8+
// create a timeout to reject promise if not resolved
9+
let timer = setTimeout(function() {
10+
reject(new Error('promise timeout'))
11+
}, ms)
12+
13+
promise
14+
.then(function(res) {
15+
clearTimeout(timer)
16+
resolve(res)
17+
})
18+
.catch(function(err) {
19+
clearTimeout(timer)
20+
reject(err)
21+
})
22+
})
23+
}

source/views/streaming/radio.js

Lines changed: 103 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,25 @@ import Icon from 'react-native-vector-icons/Ionicons'
1818
import Video from 'react-native-video'
1919
import {Touchable} from '../components/touchable'
2020
import {TabBarIcon} from '../components/tabbar-icon'
21+
import {promiseTimeout} from '../../lib/promise-timeout'
2122

2223
const kstoStream = 'https://cdn.stobcm.com/radio/ksto1.stream/master.m3u8'
24+
const kstoStatus = 'https://cdn.stobcm.com/radio/ksto1.stream/chunklist.3mu8'
2325
const image = require('../../../images/streaming/ksto/ksto-logo.png')
2426

2527
type Viewport = {
2628
width: number,
2729
height: number,
2830
}
2931

32+
type PlayState = 'paused' | 'playing' | 'checking'
33+
3034
type Props = {}
3135

3236
type State = {
33-
refreshing: boolean,
34-
paused: boolean,
37+
playState: PlayState,
3538
streamError: ?Object,
39+
uplinkError: ?string,
3640
viewport: Viewport,
3741
}
3842

@@ -43,9 +47,9 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
4347
}
4448

4549
state = {
46-
refreshing: false,
47-
paused: true,
50+
playState: 'paused',
4851
streamError: null,
52+
uplinkError: null,
4953
viewport: Dimensions.get('window'),
5054
}
5155

@@ -61,14 +65,67 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
6165
this.setState(() => ({viewport: event.window}))
6266
}
6367

64-
changeControl = () => {
65-
this.setState(state => ({paused: !state.paused}))
68+
// check the stream uplink status
69+
isUplinkUp = async () => {
70+
try {
71+
await promiseTimeout(6000, fetch(kstoStatus))
72+
return true
73+
} catch (err) {
74+
return false
75+
}
76+
}
77+
78+
onPlay = async () => {
79+
this.setState(() => ({playState: 'checking'}))
80+
81+
const uplinkStatus = await this.isUplinkUp()
82+
83+
if (uplinkStatus) {
84+
this.setState(() => ({playState: 'playing'}))
85+
} else {
86+
this.setState(() => ({
87+
playState: 'paused',
88+
uplinkError: 'The KSTO stream is down. Sorry!',
89+
}))
90+
}
91+
}
92+
93+
onPause = () => {
94+
this.setState(() => ({
95+
playState: 'paused',
96+
uplinkError: null,
97+
}))
6698
}
6799

68100
// error from react-native-video
69101
onError = (e: any) => {
70-
this.setState(() => ({streamError: e, paused: true}))
71-
console.log(e)
102+
this.setState(() => ({streamError: e, playState: 'paused'}))
103+
}
104+
105+
renderButton = (state: PlayState) => {
106+
switch (state) {
107+
case 'paused':
108+
return (
109+
<ActionButton icon="ios-play" text="Listen" onPress={this.onPlay} />
110+
)
111+
112+
case 'checking':
113+
return (
114+
<ActionButton
115+
icon="ios-more"
116+
text="Starting"
117+
onPress={this.onPause}
118+
/>
119+
)
120+
121+
case 'playing':
122+
return (
123+
<ActionButton icon="ios-pause" text="Pause" onPress={this.onPause} />
124+
)
125+
126+
default:
127+
return <ActionButton icon="ios-bug" text="Error" onPress={() => {}} />
128+
}
72129
}
73130

74131
render() {
@@ -84,6 +141,12 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
84141
height: logoWidth,
85142
}
86143

144+
const error = this.state.uplinkError
145+
? <Text style={styles.status}>{this.state.uplinkError}</Text>
146+
: null
147+
148+
const button = this.renderButton(this.state.playState)
149+
87150
return (
88151
<ScrollView
89152
contentContainerStyle={[styles.root, sideways && landscape.root]}
@@ -99,17 +162,15 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
99162
<View style={styles.container}>
100163
<Title />
101164

102-
<PlayPauseButton
103-
onPress={this.changeControl}
104-
paused={this.state.paused}
105-
/>
165+
{error}
166+
{button}
106167

107-
{!this.state.paused
168+
{this.state.playState === 'playing'
108169
? <Video
109170
source={{uri: kstoStream}}
110171
playInBackground={true}
111172
playWhenInactive={true}
112-
paused={this.state.paused}
173+
paused={this.state.playState !== 'playing'}
113174
onError={this.onError}
114175
/>
115176
: null}
@@ -119,46 +180,30 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
119180
}
120181
}
121182

122-
const Title = () => {
123-
return (
124-
<View style={styles.titleWrapper}>
125-
<Text selectable={true} style={styles.heading}>
126-
St. Olaf College Radio
127-
</Text>
128-
<Text selectable={true} style={styles.subHeading}>
129-
KSTO 93.1 FM
130-
</Text>
131-
</View>
132-
)
133-
}
183+
const Title = () =>
184+
<View style={styles.titleWrapper}>
185+
<Text selectable={true} style={styles.heading}>
186+
St. Olaf College Radio
187+
</Text>
188+
<Text selectable={true} style={styles.subHeading}>
189+
KSTO 93.1 FM
190+
</Text>
191+
</View>
134192

135-
class PlayPauseButton extends React.PureComponent {
136-
props: {
137-
paused: boolean,
138-
onPress: () => any,
139-
}
140-
141-
render() {
142-
const {paused, onPress} = this.props
143-
return (
144-
<Touchable
145-
style={buttonStyles.button}
146-
hightlight={false}
147-
onPress={onPress}
148-
>
149-
<View style={buttonStyles.buttonWrapper}>
150-
<Icon
151-
style={buttonStyles.icon}
152-
name={paused ? 'ios-play' : 'ios-pause'}
153-
/>
154-
<Text style={buttonStyles.action}>
155-
{paused ? 'Listen' : 'Pause'}
156-
</Text>
157-
</View>
158-
</Touchable>
159-
)
160-
}
193+
type ActionButtonProps = {
194+
icon: string,
195+
text: string,
196+
onPress: () => any,
161197
}
198+
const ActionButton = ({icon, text, onPress}: ActionButtonProps) =>
199+
<Touchable style={buttonStyles.button} hightlight={false} onPress={onPress}>
200+
<View style={buttonStyles.buttonWrapper}>
201+
<Icon style={buttonStyles.icon} name={icon} />
202+
<Text style={buttonStyles.action}>
203+
{text}
204+
</Text>
205+
</View>
206+
</Touchable>
162207

163208
const styles = StyleSheet.create({
164209
root: {
@@ -200,6 +245,12 @@ const styles = StyleSheet.create({
200245
fontSize: 28,
201246
textAlign: 'center',
202247
},
248+
status: {
249+
paddingTop: 10,
250+
fontWeight: '400',
251+
fontSize: 24,
252+
color: c.grapefruit,
253+
},
203254
})
204255

205256
const landscape = StyleSheet.create({

0 commit comments

Comments
 (0)