Stuck with audio streaming

I’m working on a new channel and trying to get streaming of audio tracks to work. I have a URL service all set up and the clients see the lists of tracks and so on just fine. The web client can also play the track just fine but other clients seem to have problems.

Android just seems to keep re-requesting the track stream and never plays anything.

Xbox One starts playing the track but shows up an error message saying that something has gone wrong.

My Vizio smart TV displays a red warning that the media server is not responding then skips to the next track.

My code is pretty straightforward, here is a snippet from the url service:

def MediaObjectsForTrack(library, track):
    return [
        MediaObject(
            bitrate=320,
            container=Container.MP3,
            audio_codec=AudioCodec.MP3,
            duration=track.duration,
            parts=[PartObject(
                key=Callback(
                    LibraryTrackStream,
                    libraryId=library.id,
                    trackId=track.id,
                    quality="hi"
                ),
                duration=track.duration,
                streams=[
                    AudioStreamObject(
                        selected=1,
                        bitrate=320,
                        codec=AudioCodec.MP3,
                        duration=track.duration
                    )
                ]
            )]
        )
    ]


def LibraryTrackStream(libraryId, trackId, quality, **kwargs):
    library = music.get_library(libraryId)
    client = library.get_stream_client()
    url = client.get_stream_url(trackId, None, quality)
    client.logout()
    return Redirect(url)

I suspect I may need to use IndirectResponse instead of redirect but I can’t get that to work with any clients. Does anyone have any suggestions?

Also right now the client gets the actual url of the stream to play, I’d prefer having the media server act as a middle-man there and maybe transcode it so the server doesn’t see multiple IP addresses accessing the media at the same time.

@EnglishMossop

You probably could get the idea if you could get to the clients log files.

Anyway, it may be a very long shot, but for what it’s worth, I’ve experienced some weird issues with Plex Home Theater not wanting to play any audio, until I added a route that contained file extension to the the function that returns a Redirect to the actual URL. It started working all of a sudden, but don’t ask me why :slight_smile: Routes wouldn’t work in the URL Services though, you’d have to move the function to the Code section.

Example:

@route(PREFIX+"/play/{libraryId}-{trackId}-{quality}.{ext}")
def LibraryTrackStream(libraryId, trackId, quality, **kwargs):
    ...

Thanks, I’ve tried all of that already though.

I do not create many audio only URL services, but I am not sure about your “streams” variable in the PartObject. According to the Framework, the only variables listed for a PartObject are key and duration.

And there is no way to force a Plex player to transcode, that is the decision of each Plex player app. The channel plugin code just build the media object for the media stream and then each Plex player app reads that info and decides the best way to play it. But if you provide less variable data about the media object, it could force the player app to transcode the media, because it may not have enough data to choose to direct play it.

The best place to get working examples for audio URL services would be the Services.bundle in Github - github.com/plexinc-plugins/Services.bundle. Try putting in a search value like “AudioCodec.MP3” in the “search this repository” field and it will return some example ServiceCode.pys for audio URL services.

Couple weeks ago I was working out how to build streams for the PartObject because I was trying to create a new SubtitleStreamObject within the Framework code. I found that if I built the streams myself and didn’t let the Framework do it, then some inheritance was lost and all my clients would fall back to transcoding the media. So this could be part of your issue between clients since you’re building the streams.

Also, you don’t need to specify the AudioStreamObject, because the Framework will do all that for you. If you’re building multiple PartObject per MediaObject then the duration within the PartObject is useful. Otherwise, it’s not needed. You have it specified within the MediaObject, so the Framework will copy it into the PartObject if there is only one part within the parts list.

Some versions ago of PMS, there was a change, and now the MediaObject always needs the audio_channels set. Not sure if this is a hard rule, but if you look through the Services.bundle commit history on Github, you will see a period when these were being added. Otherwise, depending on the client, users were experiencing playback issues.

The code you included does not have a MediaObjectsForURL, which is required for Service Code located within the Services directory of a channel. So not sure where you’re implementing your MediaObjectsForTrack code.

Nonetheless, I’ve included an example of how to use the indirect callback for a TrackObject.

def MediaObjectsForTrack(library, track):
    return [
        MediaObject(
            bitrate=320,
            duration=track.duration,
            container=Container.MP3,
            audio_codec=AudioCodec.MP3,
            audio_channels=2,
            optimized_for_streaming=True,
            parts=[
                PartObject(
                    key=Callback(
                        LibraryTrackStream,
                        libraryId=library.id,
                        trackId=track.id,
                        quality="hi"
                        )
                    ]
                )
            ]

@indirect
def LibraryTrackStream(libraryId, trackId, quality, **kwargs):
    library = music.get_library(libraryId)
    client = library.get_stream_client()
    url = client.get_stream_url(trackId, None, quality)
    client.logout()
    return IndirectResponse(TrackObject, key=url)

If you want to always force transcoding, then use the following:

def MediaObjectsForTrack(library, track):
    return [
        MediaObject(
            bitrate=320,
            duration=track.duration,
            audio_channels=2,
            optimized_for_streaming=False,
            parts=[
                PartObject(
                    key=Callback(
                        LibraryTrackStream,
                        libraryId=library.id,
                        trackId=track.id,
                        quality="hi"
                        )
                    ]
                )
            ]

@indirect
def LibraryTrackStream(libraryId, trackId, quality, **kwargs):
    library = music.get_library(libraryId)
    client = library.get_stream_client()
    url = client.get_stream_url(trackId, None, quality)
    client.logout()
    return IndirectResponse(TrackObject, key=url)

As-long-as you don’t give the Framework the codecs and container, then it will be forced to transcode the media.


If the service code is not within the Services directory, and instead within your __init__.py file (or anywhere within the Contents/Code directory), then you will need to include the @route for each function.

I just remembered something: if you don’t really care about transconding and only want PMS to not disclose the “real” URLs, you may try to use DataObject:

...
return DataObject(payload, 'audio/mpeg')

The problem here is that payload must be contents of the audio track, and if you use it like this, it’ll have to wait until it’s downloaded, and only then it can be sent to the client. Even though internet speed nowadays is better than it used to be, there still may be a noticeable lag. Maybe it’s possible to use some stream wrapper (perhaps urllib2.urlopen?) that will download and serve the content to the clients at the same time.

Another problem may be if it turns out that serving raw data makes your channel or URL Service unresponsive or “busy” for the duration (generally client-server apps like PMS should handle that well, but who knows), since it’s a big difference between handing a redirect response and outputting a whole audio file.

Thanks for all your help, it’s really appreciated. Turns out my mistake that was breaking my use of IndirectResponse was in not using the @indirect decorator. Fixing that got playback working on Android but still no joy with Xbox or my smart TV. Both of them report that the media server is not responding in some way even though the Xbox actually starts playing the track.

I tried removing the container and audio_codec options and in either case it broke playback in the web interface even with audio_channels added.

@Twoure: Oops, I just found the streams variable listed in the Missing Documentation.

@shopgirl284 said:
@Twoure: Oops, I just found the streams variable listed in the Missing Documentation.

For further clarification, from the Plex Framework source code objectkit.py file (PMS v1.1.4):

class PartObject(Framework.modelling.objects.Container, ObjectWithPOSTCallback):
  xml_tag = 'Part'

  _children_attr_name = 'streams'
  _child_types = [AudioStreamObject, VideoStreamObject]
  _attribute_list = [
    'key',
    'file',
    'duration',
    'http_headers',

    # New attributes migrated from MediaObject
    'container',
    'optimized_for_streaming',
    'duration',
    'protocol',
  ]

  def __init__(self, **kwargs):
    if 'file' not in kwargs:
      kwargs['file'] = ''
    Framework.modelling.objects.Container.__init__(self, **kwargs)

  def _to_xml(self):
    el = Framework.modelling.objects.Container._to_xml(self)
    ObjectWithPOSTCallback._apply_post_url_and_headers(self.key, el)
    return el