How to play audio content in SDK videoplayer

For those of you who have been asking about how to modify the videoplayer example SDK app to play audio, here’s a solution:

  1. In the XML file for your feed, set to mp3 or wma, as appropriate. The tag should be subordinate to the tag for your content, the tag being under the tag. You can mix audio and video content in the same feed as long as the tags for each are set appropriately.

  2. Replace appDetailScreen.brs with:


'*******************************************************************
'**  Video Player Example Application - Detail Screen 
'**  November 2009
'**  Copyright(c) 2009 Roku Inc. All Rights Reserved.
'*******************************************************************
'
' Modified by belltown to support mp3 and wma audio streams.
'
' All you need to do to handle an audio stream is to replace
' appDetailScreen.brs in the videoplayer SDK example with this file,
' then in the XML file set <StreamFormat> for your audio item
' to mp3 or wma.
'*******************************************************************

Function VIDEO_BUTTON_PLAY()	As Integer : Return 1 : End Function
Function VIDEO_BUTTON_RESUME()	As Integer : Return 2 : End Function
Function AUDIO_BUTTON_PLAY()	As Integer : Return 3 : End Function
Function AUDIO_BUTTON_STOP()	As Integer : Return 4 : End Function
Function AUDIO_BUTTON_PAUSE()	As Integer : Return 5 : End Function
Function AUDIO_BUTTON_RESUME()	As Integer : Return 6 : End Function
 
Function preShowDetailScreen(breadA=invalid, breadB=invalid) As Object
	port=CreateObject("roMessagePort")
	screen = CreateObject("roSpringboardScreen")
	' The description style is now set in refreshShowDetail once it is known whether the content is audio or video
	'screen.SetDescriptionStyle("video")
	screen.SetMessagePort(port)
	if breadA<>invalid and breadB<>invalid then
		screen.SetBreadcrumbText(breadA, breadB)
	end if

	return screen
End Function

'***************************************************************
'** The show detail screen(springboard) is where the user sees
'** the details for a show and is allowed to select a show to
'** begin playback.  This is the main event loop for that screen
'** and where we spend our time waiting until the user presses a
'** button and then we decide how best to handle the event.
'***************************************************************
Function showDetailScreen(screen As Object, showList As Object, showIndex as Integer) As Integer

	if validateParam(screen, "roSpringboardScreen", "showDetailScreen") = false return -1
	if validateParam(showList, "roArray", "showDetailScreen") = false return -1

	' Set up the audio player
	audio = CreateObject("roAudioPlayer")
	audio.SetMessagePort(screen.GetMessagePort())
	audio.SetLoop(0)
	audio.SetNext(0)

	' Display the Springboard screen
	show = showList[showIndex]
	refreshShowDetail(screen, audio, showList, showIndex)

	'remote key id's for left/right navigation
	remoteKeyLeft  = 4
	remoteKeyRight = 5

	isPlaying = false	' Set to true when the user presses the Play button on the audio Springboard screen
	isPaused = false	' Set to true when the user presses the Pause button on the audio Springboard screen
	isBuffering = true	' Set to true when audio is buffering or waiting for play to start

	while true
		' Use a timeout on the wait loop to allow for updating of the audio progress bar
		' The smaller the timeout value, the smoother the updates to the progress bar
		msg = wait(100, screen.GetMessagePort())

		if msg = invalid		' Wait Timeout - update the audio progress bar
			if isAudioStream(show) and isPlaying and not isBuffering
				runtimeElapsed = setProgressBar(screen, show)
				' roAudioPlayer has a bug where if the audio player is paused immediately before the content finishes playing
				' (within half a second or so), then the events that signify the end of the content do not fire.
				' The 'if' statement below is a workaround for that bug. Assuming the runtime has been set correctly for
				' the audio content item, if the audio content has reached its runtime then the audio player is stopped.
				' Uncomment the 'if' statement below if you care enough to implement this workaround ...
				REM if runtimeElapsed
					REM print "Terminating play after runtime has elapsed"
					REM isPlaying = false
					REM isPaused = false
					REM isBuffering = true
					REM audio.Stop()
					REM zeroProgressBar(screen, show)
					REM screen.ClearButtons()
					REM screen.AddButton(AUDIO_BUTTON_PLAY(), "Play")					
				REM endif
			endif
		else if type(msg) = "roSpringboardScreenEvent"
			if msg.isScreenClosed()
				print "Screen closed"
				exit while
			else if msg.isRemoteKeyPressed()
				key = msg.GetIndex()
				print "Remote key pressed: "; key
				if key = remoteKeyLeft			' Play the previous content item
					isPlaying = false
					isPaused = false
					audio.Stop()
					audio.ClearContent()
					showIndex = getPrevShow(showList, showIndex)
					if showIndex <> -1
						refreshShowDetail(screen, audio, showList, showIndex)
						show = showList[showIndex]
					endif
				else if key = remoteKeyRight	' Play the next content item
					isPlaying = false
					isPaused = false
 					audio.Stop()
					audio.ClearContent()
					showIndex = getNextShow(showList, showIndex)
					if showIndex <> -1
						refreshShowDetail(screen, audio, showList, showIndex)
						show = showList[showIndex]
					endif
				endif
			else if msg.isButtonPressed()
				button = msg.GetIndex()
				if button = VIDEO_BUTTON_RESUME()
					PlayStart = RegRead(showList[showIndex].ContentId)
					if PlayStart <> invalid
						showList[showIndex].PlayStart = PlayStart.ToInt()
					endif
					showVideoScreen(showList[showIndex])
					refreshShowDetail(screen, audio, showList, showIndex)
				else if button = VIDEO_BUTTON_PLAY()
					showList[showIndex].PlayStart = 0
					showVideoScreen(showList[showIndex])
					refreshShowDetail(screen, audio, showList, showIndex)
				else if button = AUDIO_BUTTON_PLAY()
					isPlaying = true
					isPaused = false
					audio.Play()
					show.CurrentPosition = 0
					zeroProgressBar(screen, show)
					screen.ClearButtons()
					screen.AddButton(AUDIO_BUTTON_PAUSE(), "Pause")
					screen.AddButton(AUDIO_BUTTON_STOP(), "Stop")
				else if button = AUDIO_BUTTON_STOP()
					isPlaying = false
					isPaused = false
					isBuffering = true
					audio.Stop()
					show.CurrentPosition = 0
					zeroProgressBar(screen, show)
					screen.ClearButtons()
					screen.AddButton(AUDIO_BUTTON_PLAY(), "Play")
				else if button = AUDIO_BUTTON_PAUSE()
					isPlaying = false
					isPaused = true
					audio.Pause()
					screen.ClearButtons()
					screen.AddButton(AUDIO_BUTTON_RESUME(), "Resume")
					screen.AddButton(AUDIO_BUTTON_STOP(), "Stop")
 				else if button = AUDIO_BUTTON_RESUME()
					isPlaying = true
					isPaused = false
					audio.Resume()
					screen.ClearButtons()
					screen.AddButton(AUDIO_BUTTON_PAUSE(), "Pause")
					screen.AddButton(AUDIO_BUTTON_STOP(), "Stop")
				else
					print "Invalid button pressed"
				endif
				print "Button pressed:"; button " " msg.GetData()
			endif
		else if type(msg) = "roAudioPlayerEvent"
			if msg.IsRequestSucceeded() or msg.IsRequestFailed() or msg.IsPartialResult() or msg.IsFullResult()
				if msg.IsRequestSucceeded() then print "roAudioPlayerEvent: IsRequestSucceeded"
				if msg.IsRequestFailed() then print "roAudioPlayerEvent: IsRequestFailed: "; msg.GetMessage ()
				if msg.IsPartialResult() then print "roAudioPlayerEvent: IsPartialResult"
				if msg.IsFullResult() then print "roAudioPlayerEvent: IsFullResult"
				isPlaying = false
				isPaused = false
				isBuffering = true
				audio.Stop()
				zeroProgressBar(screen, show)
				screen.ClearButtons()
				screen.AddButton(AUDIO_BUTTON_PLAY(), "Play")
			else if msg.IsPaused()
				print "roAudioPlayerEvent: IsPaused"
				if not isBuffering		' If we're buffering, the current position has already been stored
					show.CurrentPosition = show.CurrentPosition + show.PlaybackTimer.TotalMilliseconds()
				endif
			else if msg.IsResumed()
				print "roAudioPlayerEvent: IsResumed"
				show.PlaybackTimer.Mark()
			else if msg.IsStatusMessage()
				message = msg.GetMessage()
				print "roAudioPlayerEvent: IsStatusMessage: "; message
				if message = "startup progress"
					if not isBuffering		' Audio player has started buffering - reset playback timer
						show.CurrentPosition = show.CurrentPosition + show.PlaybackTimer.TotalMilliseconds()
						isBuffering = true
					endif
				else if message = "start of play"
					show.PlaybackTimer.Mark()
					isBuffering = false
				endif 
			else if msg.IsListItemSelected()
				print "roAudioPlayerEvent: IsListItemSelected"
			else if msg.IsStreamStarted()
				print "roAudioPlayerEvent: IsStreamStarted"
			else
				print "roAudioPlayerEvent: Unknown Event:"; msg.GetType()
			endif
		else
			print "Unexpected message class: "; type(msg)
		endif
	end while

	return showIndex

End Function

'**************************************************************
'** Refresh the contents of the show detail screen. This may be
'** required on initial entry to the screen or as the user moves
'** left/right on the springboard.  When the user is on the
'** springboard, we generally let them press left/right arrow keys
'** to navigate to the previous/next show in a circular manner.
'** When leaving the screen, the should be positioned on the 
'** corresponding item in the poster screen matching the current show
'**************************************************************
Function refreshShowDetail(screen As Object, audio As Object, showList As Object, showIndex as Integer) As Integer

	if validateParam(screen, "roSpringboardScreen", "refreshShowDetail") = false return -1
	if validateParam(audio, "roAudioPlayer", "refreshShowDetail") = false return -1
	if validateParam(showList, "roArray", "refreshShowDetail") = false return -1

	show = showList[showIndex]
	show.PlaybackTimer = CreateObject("roTimespan")

	'Uncomment this statement to dump the details for each show
	'PrintAA(show)

	if isAudioStream(show)
		if show.StreamUrls.Count() > 0
			show.Url = show.StreamUrls[0]
		else
			print "No audio stream found"
		endif
		audio.AddContent(show)
		show.CurrentPosition = 0
		if show.Runtime <> "" and show.Runtime.ToInt() > 0		
			show.ContentType = "audio"		' ContentType controls the size of the artwork and whether a progress bar is allowed
			screen.SetProgressIndicatorEnabled(true)
			zeroProgressBar(screen, show)
		else
			screen.SetProgressIndicatorEnabled(false)
		endif
		'
		' You can set the springboard screen's description style depending on which meta-data fields you want displayed.
		'
		' "audio": All tags on audio screen are substituted with Content Meta-Data (Album and Artist).
		' "movie": All tags on the video screen are substituted with Content Meta-Data
		' "video": All tags except <Directors> and <Ratings> on the video screen are replaced with Content Meta-Data.
		' "generic": Only the <Title> and <Description> tags on the video screen are replaced with Content Meta-Data.
		'
		screen.SetDescriptionStyle("movie")
		screen.ClearButtons()
		screen.AddButton(AUDIO_BUTTON_PLAY(), "Play")
		screen.SetContent(show)
		screen.Show()
	else
		' Changed the description type from "video" in the original Roku SDK videoplayer example to "movie",
		' so that more content Meta-Data items are displayed on the Springboard screen.
		screen.SetDescriptionStyle("movie")
		screen.ClearButtons()
		if regRead(show.contentid) <> invalid and regRead(show.contentid).ToInt() >= 30
			screen.AddButton(VIDEO_BUTTON_RESUME(), "Resume playing")
			screen.AddButton(VIDEO_BUTTON_PLAY(), "Play from beginning")
		else
			screen.addbutton(VIDEO_BUTTON_PLAY(),"Play")
		endif
		screen.SetContent(show)
		screen.Show()
	endif

End Function

'********************************************************
'** Get the next item in the list and handle the wrap 
'** around case to implement a circular list for left/right 
'** navigation on the springboard screen
'********************************************************
Function getNextShow(showList As Object, showIndex As Integer) As Integer
	if validateParam(showList, "roArray", "getNextShow") = false return -1

	nextIndex = showIndex + 1
	if nextIndex >= showList.Count() or nextIndex < 0
		nextIndex = 0
	endif

	show = showList[nextIndex]
	if validateParam(show, "roAssociativeArray", "getNextShow") = false return -1

	return nextIndex
End Function

'********************************************************
'** Get the previous item in the list and handle the wrap 
'** around case to implement a circular list for left/right 
'** navigation on the springboard screen
'********************************************************
Function getPrevShow(showList As Object, showIndex As Integer) As Integer
	if validateParam(showList, "roArray", "getPrevShow") = false return -1

	prevIndex = showIndex - 1
	if prevIndex < 0 or prevIndex >= showList.Count()
		if showList.Count() > 0 then
			prevIndex = showList.Count() - 1
		else
			return -1
		endif
	endif

	show = showList[prevIndex]
	if validateParam(show, "roAssociativeArray", "getPrevShow") = false return -1

	return prevIndex
End Function

'********************************************************************
'** Return true if the content's StreamFormat denotes an audio stream
'********************************************************************
Function isAudioStream(show As Object) As Boolean
	return LCase(show.StreamFormat) = "mp3" or LCase(show.StreamFormat) = "wma"
End Function

'*******************************
'** Reset the audio progress bar
'*******************************
Function zeroProgressBar(screen As Object, show As Object) As Void
	if show.Runtime <> "" and show.Runtime.ToInt() > 0
		screen.SetProgressIndicator(0, show.Runtime.ToInt())
	endif
End Function

'*************************************************
'** Update the audio progress bar
'** If the show's runtime has elapsed, return true
'*************************************************
Function setProgressBar(screen As Object, show As Object) As Boolean
	if show.Runtime <> "" and show.Runtime.ToInt() > 0
		' Update the progress indicator
		screen.SetProgressIndicator(show.CurrentPosition + show.PlaybackTimer.TotalMilliseconds(), show.Runtime.ToInt() * 1000)
		' Workaround for roAudioplayer bug where no event is fired when the end of the content is reached if the player was paused immediately before the end
		return (show.CurrentPosition + show.PlaybackTimer.TotalMilliseconds ()) > (show.Runtime.ToInt () * 1000)
	endif
	return false
End Function

This will display a springboard screen for the audio content containing Play/Pause/Resume/Stop buttons. If the tag is present and set to a non-zero value, then an audio player progress bar will also be displayed along the bottom edge of the artwork.

I found this out by accident about a week ago. At least the answer is simple.
DA

Bandal, you are Heaven Sent !!!

This is exactly what I needed for my Channel as well!!

Bless you, Bless You !!!

I believe you are the most helpful person in this forum!!

You can also play Live Internet Streams by placing in the streamurl the actual link and Port with the slash or / at the end. Here is one to try. Set StreamFormat to mp3.
http://67.228.150.184:9015/

I was able to get it with a pc program called UrlGetter and play a Shoutcast Stream in VLC while UrlGetter is running and you can find the address and port number.
Credit to belltown.

Thanks,

DA

See, I don’t understand that - the need for a ‘ripper’ program for Shoutcast?
That makes about as much sense as someone paying for a screenshot program because they can’t be troubled to use printscreen and paste, lol.

All the currently broadcasting shoutcast servers are listed here: http://www.shoutcast.com/radio/List
You can search for whatever you want to listen to - when it shows up in the list, right click the name and “save link as” which saves a .PLS file to your hard drive.
Open it in notepad and there is the URL you need in plain text right there.

Thanks, I knew there must be an easier way. Just never took the time to get this far as I needed it only 2 times. Thanks for the info looks way easier now.
DA

“Gary1980Arb” wrote:
Bandal, you are Heaven Sent !!!

This is exactly what I needed for my Channel as well!!

Bless you, Bless You !!!

I believe you are the most helpful person in this forum!!

I believe you meant to thank belltown and I couldn’t agree more. Belltown continues to be a great asset to this forum. My thanks as well!

I agree.

Thank you belltown !!

I implemented this code, but I have one bug I just can’t figure out when playing a shoutcast/icecast stream.

On my Roku 2XD it sometimes buffers for up to 2 minutes, then starts playing fine. Occasionally it starts after 2 or 3 seconds.

I thought it was the shoutcast stream or my wireless connection, but then I tried it on my Roku 3 in a room that is farther away from my router and it worked fine every time.

Any ideas?

“RobSMS” wrote:
I implemented this code, but I have one bug I just can’t figure out when playing a shoutcast/icecast stream.

On my Roku 2XD it sometimes buffers for up to 2 minutes, then starts playing fine. Occasionally it starts after 2 or 3 seconds.

I thought it was the shoutcast stream or my wireless connection, but then I tried it on my Roku 3 in a room that is farther away from my router and it worked fine every time.

Any ideas?
It appears to be a long-standing bug in roAudioPlayer. I played around with various workarounds. What I’m currently doing is when I get the audio player’s IsStreamStarted event, Pause then Resume the audio player, giving it a little Sleep time to think about it. It seems to work about 98% of the time. Something like this:


Sleep (350)
audio.Pause ()
Sleep (350)
audio.Resume ()

It doesn’t seem very effective on the 3.1 firmware though. It still takes a while to buffer in that case. However, I’ll change the text on the roSpringboardScreen’s Play/Pause/Resume button to display a percentage showing how much buffering has been done, in response to the IsStatusMessage “startup progress” message so the user knows something is really happening (albeit slowly) rather than the channel having hung. I know this works on the Roku 2XS. I don’t have a Roku 3, so I’ve no idea how it works (or if it’s necessary) on that unit.

Yeah, that seems to have fixed the issue. I like the idea of updating the buffer onscreen, thanks for the tips :slightly_smiling_face:

Thank you @belltown for posting this. I wish I’d found this a few months ago. I actually wound up using this method for the channels I’ve been working on accidentally. I was trying to create audio channels using RSS feeds and the MRSS example only used video samples. After trying to modify the example Audio Player code, which uses roAudioPlayer, I just changed the in the videoplayer to mp3 and it worked and I was easily able to pull in an RSS feed to fill it.

I didn’t change the appDetailScreen so I’ll definitely look into that as currently it plays but shows no detail on the MP3 Audio stream, other than the title. I’ve seen that most Audio only channels play the audio while continuing to display the individual episode’s screen, with my example it goes to the uiVideoScreen.brs screen and just shows the title and progress, which isn’t horrible but with audio, I’d like to keep displaying the individual episode screen, NWM_MRSS.brs in my case. I’ll look into the appDetailScreen.brs changes, although I don’t see that in the MRSS example, but I’ll look for it in the videoplayer example and play around with it.