Skip to content

Commit ab1dbf0

Browse files
committed
remove use of react-native-video from KSTO screen
1 parent 42ab53e commit ab1dbf0

File tree

1 file changed

+233
-56
lines changed

1 file changed

+233
-56
lines changed

source/views/streaming/radio.js

Lines changed: 233 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,33 @@
66

77
import React from 'react'
88
import {
9-
StyleSheet,
10-
View,
11-
ScrollView,
12-
Text,
139
Dimensions,
1410
Image,
11+
ScrollView,
12+
StyleSheet,
13+
Text,
14+
View,
15+
WebView,
1516
} from 'react-native'
1617
import * as c from '../components/colors'
1718
import Icon from 'react-native-vector-icons/Ionicons'
18-
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'
2221

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

27-
type Viewport = {
28-
width: number,
29-
height: number,
30-
}
25+
type Viewport = {width: number, height: number, }
3126

32-
type PlayState = 'paused' | 'playing' | 'checking'
27+
type HtmlAudioError = {code: number, message: string}
28+
29+
type PlayState = 'paused' | 'playing' | 'checking' | 'loading'
3330

3431
type Props = {}
3532

3633
type State = {
3734
playState: PlayState,
38-
streamError: ?Object,
35+
streamError: ?HtmlAudioError,
3936
uplinkError: ?string,
4037
viewport: Viewport,
4138
}
@@ -65,62 +62,45 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
6562
this.setState(() => ({viewport: event.window}))
6663
}
6764

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-
}
65+
play = () => {
66+
this.setState(() => ({playState: 'checking'}))
7667
}
7768

78-
onPlay = async () => {
79-
this.setState(() => ({playState: 'checking'}))
69+
pause = () => {
70+
this.setState(() => ({playState: 'paused'}))
71+
}
8072

81-
const uplinkStatus = await this.isUplinkUp()
73+
handleStreamPlay = () => {
74+
this.setState(() => ({playState: 'playing'}))
75+
}
8276

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-
}
77+
handleStreamPause = () => {
78+
this.setState(() => ({playState: 'paused'}))
9179
}
9280

93-
onPause = () => {
94-
this.setState(() => ({
95-
playState: 'paused',
96-
uplinkError: null,
97-
}))
81+
handleStreamEnd = () => {
82+
this.setState(() => ({playState: 'paused'}))
9883
}
9984

100-
// error from react-native-video
101-
onError = (e: any) => {
85+
handleStreamError = (e: {code: number, message: string}) => {
10286
this.setState(() => ({streamError: e, playState: 'paused'}))
10387
}
10488

10589
renderButton = (state: PlayState) => {
10690
switch (state) {
10791
case 'paused':
10892
return (
109-
<ActionButton icon="ios-play" text="Listen" onPress={this.onPlay} />
93+
<ActionButton icon="ios-play" text="Listen" onPress={this.play} />
11094
)
11195

11296
case 'checking':
11397
return (
114-
<ActionButton
115-
icon="ios-more"
116-
text="Starting"
117-
onPress={this.onPause}
118-
/>
98+
<ActionButton icon="ios-more" text="Starting" onPress={this.pause} />
11999
)
120100

121101
case 'playing':
122102
return (
123-
<ActionButton icon="ios-pause" text="Pause" onPress={this.onPause} />
103+
<ActionButton icon="ios-pause" text="Pause" onPress={this.pause} />
124104
)
125105

126106
default:
@@ -143,7 +123,12 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
143123

144124
const error = this.state.uplinkError
145125
? <Text style={styles.status}>{this.state.uplinkError}</Text>
146-
: null
126+
: this.state.streamError
127+
? <Text style={styles.status}>
128+
Error Code {this.state.streamError.code}:{' '}
129+
{this.state.streamError.message}
130+
</Text>
131+
: null
147132

148133
const button = this.renderButton(this.state.playState)
149134

@@ -165,15 +150,15 @@ export default class KSTOView extends React.PureComponent<void, Props, State> {
165150
{error}
166151
{button}
167152

168-
{this.state.playState === 'playing'
169-
? <Video
170-
source={{uri: kstoStream}}
171-
playInBackground={true}
172-
playWhenInactive={true}
173-
paused={this.state.playState !== 'playing'}
174-
onError={this.onError}
175-
/>
176-
: null}
153+
<StreamPlayer
154+
playState={this.state.playState}
155+
// onWaiting={this.handleStreamWait}
156+
onEnded={this.handleStreamEnd}
157+
// onStalled={this.handleStreamStall}
158+
onPlay={this.handleStreamPlay}
159+
onPause={this.handleStreamPause}
160+
onError={this.handleStreamError}
161+
/>
177162
</View>
178163
</ScrollView>
179164
)
@@ -190,11 +175,200 @@ const Title = () =>
190175
</Text>
191176
</View>
192177

178+
type StreamPlayerProps = {
179+
playState: PlayState,
180+
onWaiting?: () => any,
181+
onEnded?: () => any,
182+
onStalled?: () => any,
183+
onPlay?: () => any,
184+
onPause?: () => any,
185+
onError?: HtmlAudioError => any,
186+
}
187+
188+
type HtmlAudioState =
189+
| 'waiting'
190+
| 'ended'
191+
| 'stalled'
192+
| 'playing'
193+
| 'play'
194+
| 'pause'
195+
type HtmlAudioEvent =
196+
| {type: HtmlAudioState}
197+
| {type: 'error', error: HtmlAudioError}
198+
199+
class StreamPlayer extends React.PureComponent<void, StreamPlayerProps, void> {
200+
_webview: WebView
201+
202+
componentWillReceiveProps(nextProps: StreamPlayerProps) {
203+
this.dispatchEvent(nextProps.playState)
204+
}
205+
206+
componentWillUnmount() {
207+
this.pause()
208+
}
209+
210+
dispatchEvent = (nextPlayState: PlayState) => {
211+
// console.log('<StreamPlayer> state changed to', nextPlayState)
212+
213+
switch (nextPlayState) {
214+
case 'paused':
215+
return this.pause()
216+
217+
case 'loading':
218+
case 'checking':
219+
case 'playing':
220+
return this.play()
221+
222+
default:
223+
return
224+
}
225+
}
226+
227+
handleMessage = (event: any) => {
228+
const data: HtmlAudioEvent = JSON.parse(event.nativeEvent.data)
229+
230+
// console.log('<audio> dispatched event', data.type)
231+
232+
switch (data.type) {
233+
case 'waiting':
234+
return this.props.onWaiting && this.props.onWaiting()
235+
236+
case 'ended':
237+
return this.props.onEnded && this.props.onEnded()
238+
239+
case 'stalled':
240+
return this.props.onStalled && this.props.onStalled()
241+
242+
case 'pause':
243+
return this.props.onPause && this.props.onPause()
244+
245+
case 'playing':
246+
case 'play':
247+
return this.props.onPlay && this.props.onPlay()
248+
249+
case 'error':
250+
return this.props.onError && this.props.onError(data.error)
251+
252+
default:
253+
return
254+
}
255+
}
256+
257+
pause = () => {
258+
// console.log('sent "pause" message to <audio>')
259+
this._webview.postMessage('pause')
260+
}
261+
262+
play = () => {
263+
// console.log('sent "play" message to <audio>')
264+
this._webview.postMessage('play')
265+
}
266+
267+
setRef = (ref: WebView) => (this._webview = ref)
268+
269+
html = url => `
270+
<style>body {background-color: white;}</style>
271+
272+
<title>KSTO Stream</title>
273+
274+
<audio id="player" webkit-playsinline>
275+
<source src="${url}" />
276+
</audio>
277+
278+
<script>
279+
var player = document.getElementById('player')
280+
281+
/////
282+
/////
283+
284+
document.addEventListener('message', function(event) {
285+
switch (event.data) {
286+
case 'play':
287+
player.play()
288+
break
289+
290+
case 'pause':
291+
player.pause()
292+
break
293+
}
294+
})
295+
296+
/////
297+
/////
298+
299+
function message(data) {
300+
window.postMessage(JSON.stringify(data))
301+
}
302+
303+
function send(event) {
304+
message({type: event.type})
305+
}
306+
307+
function error(event) {
308+
message({
309+
type: event.type,
310+
error: {
311+
code: event.target.error.code,
312+
message: event.target.error.message,
313+
},
314+
})
315+
}
316+
317+
/////
318+
/////
319+
320+
// "waiting" is fired when playback has stopped because of a temporary
321+
// lack of data.
322+
player.addEventListener('waiting', send)
323+
324+
// "ended" is fired when playback or streaming has stopped because the
325+
// end of the media was reached or because no further data is
326+
// available.
327+
player.addEventListener('ended', send)
328+
329+
// "stalled" is fired when the user agent is trying to fetch media data,
330+
// but data is unexpectedly not forthcoming.
331+
player.addEventListener('stalled', send)
332+
333+
// "playing" is fired when playback is ready to start after having been
334+
// paused or delayed due to lack of data.
335+
player.addEventListener('playing', send)
336+
337+
// "pause" is fired when playback has been paused.
338+
player.addEventListener('pause', send)
339+
340+
// "play" is fired when playback has begun.
341+
player.addEventListener('play', send)
342+
343+
// "error" is fired when an error occurs.
344+
player.addEventListener('error', error)
345+
346+
/////
347+
/////
348+
349+
player.play()
350+
</script>`
351+
352+
render() {
353+
return (
354+
<WebView
355+
ref={this.setRef}
356+
mediaPlaybackRequiresUserAction={false}
357+
allowsInlineMediaPlayback={true}
358+
source={{html: this.html(kstoStream)}}
359+
onMessage={this.handleMessage}
360+
style={styles.webview}
361+
/>
362+
)
363+
}
364+
}
365+
193366
type ActionButtonProps = {
194367
icon: string,
195368
text: string,
196369
onPress: () => any,
197370
}
371+
198372
const ActionButton = ({icon, text, onPress}: ActionButtonProps) =>
199373
<Touchable style={buttonStyles.button} hightlight={false} onPress={onPress}>
200374
<View style={buttonStyles.buttonWrapper}>
@@ -251,6 +425,9 @@ const styles = StyleSheet.create({
251425
fontSize: 24,
252426
color: c.grapefruit,
253427
},
428+
webview: {
429+
display: 'none',
430+
},
254431
})
255432

256433
const landscape = StyleSheet.create({

0 commit comments

Comments
 (0)