Play a Local M3U8 File

Oct 1, 2013 at 2:05 AM
The code is great
Your project solved a big problem for me
Now I have a new requirement.
I want to play a local M3U8 file. This file exists in isolated storage.
But I tried many ways, are not playing success. Do you have any better solutions or suggestions for me
Sorry,My English is poor,I hope you can read what I said,
Thanks for your work.
Oct 1, 2013 at 7:12 PM
All the IO is done with HttpClient, so I don't think it would understand anything about isolated storage.

If you do have to read local data, one way might be to create a custom subclass of "HttpClientHandler" that overrides "HttpClientHandler.SendAsync()" with a version that checks for and handles local storage URLs (falling back to the base class' behavior if it is a web URL). Then derive a custom subclass of "SM.Media.Web.HttpClients" that overrides "HttpClients.CreateClientHandler()" to create an instance of your isolated storage capable subclass of "HttpClientHander" instead.

I haven't actually tried this...
Oct 2, 2013 at 3:32 AM
Thanks for your reply. I think this will be a big project. To be honest my coding is weak. I will try it a little. Anyway thank you for all the work.
Oct 2, 2013 at 6:52 AM
Just to be sure: you are looking to load something from isolated storage that you have written there at runtime and can't get in MP4 format? If it is something baked into your install, then converting the HLS stream in to an MP4 and doing a "SetSource" directly on the MediaElement (or the media player) would probably be much simpler.

I haven't even tried compiling this, but I suspect this would be a reasonable starting point. The "GetStreamFromIsolatedStorage()" implementation obviously needs to do something more constructive than return null.
The only other change you would need to make is to change the "new HttpClients(...)" in your code (found in HlsView's MainPage.xaml.cs or SamplePlayer's StreamingMediaPlugin.cs) to "new IsoStorageHttpClients(...)".

Hopefully, none of the other HttpClient code will blow up when faced with a non-http/https URL...
    class IsoStorageHttpClients : HttpClients
        public IsoStorageHttpClients(Uri referrer = null, ProductInfoHeaderValue userAgent = null, ICredentials credentials = null, CookieContainer cookieContainer = null)
           :  base(referrer, userAgent, credentials, cookieContainer)
        { }

        protected override HttpClientHandler CreateClientHandler()
            var httpClientHandler = new IsoStorageHttpClientHandler();

            //if (null != _credentials)
            //    httpClientHandler.Credentials = _credentials;

            //if (null != _cookieContainer)
            //    httpClientHandler.CookieContainer = _cookieContainer;
            //    httpClientHandler.UseCookies = false;

            return httpClientHandler;

    class IsoStorageHttpClientHandler : HttpClientHandler
        static Task<Stream> GetStreamFromIsolatedStorage(string path)
            return null;

        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            if (new[] { "http", "https" }.Contains(request.RequestUri.Scheme))
                return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);

            if (request.Method != HttpMethod.Get)
                return new HttpResponseMessage(HttpStatusCode.MethodNotAllowed);

            var stream = await GetStreamFromIsolatedStorage(request.RequestUri.LocalPath).ConfigureAwait(false);

            var response = new HttpResponseMessage(HttpStatusCode.OK)
                               Content = new StreamContent(stream)

            return response;
Oct 2, 2013 at 8:49 AM
Thank you very much. I will try it .
In the case, I have some MP4 files fragmentation downloaded to Isolated Storage, I want these fragments together, because they are a whole, so I imagine if I can write a M3U8 file in your project to play the whole file. Maybe this thinking is stupid. I have no idea for this. My English is really sucks.I hope you can understand
Oct 3, 2013 at 1:51 AM
For playing MP4s, you will need to handle things a bit differently; phonesm knows only how to deal with .TS, .MP3, and .AAC segments.

My guess from putting these two threads together, is that one can do a playlist by setting up an ObservableCollection<PlaylistItem> containing URLs that start with "ms-appdata:///local/".

If you want seamless playback and the MP4 chunks you download really do belong together, you might consider writing all the parts into a single file before playing them. I'm not sure if you could persuade Microsoft's SmoothStreaming code to read from isolated storage, but if so, you would need to write the playlist as a .ism file.
Oct 3, 2013 at 3:03 AM
Edited Oct 3, 2013 at 3:51 AM
Well, It looks like I have to look for another way. The <PlaylistItem> can not meet my requirements. Perhaps the only way is to encoding them together . I try again
The "ms-appdata:///local/" way playback wrong .
I always use SetSource(Stream) way playback the loacl file.
Besides The code watch live very well the first 5 minutes,
But after 5 minutes The live will stop

BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Audio
BufferingQueue.ReportExhaustion(): Video
BufferingQueue.ReportExhaustion(): Audio
PlaylistSegmentManager.PlaylistExpiration (2013/10/3 11:41:20 +08:00)
PlaylistSegmentManager.PlaylistExpiration is starting ReadSubList (2013/10/3 11:41:20 +08:00)
PlaylistSegmentManager.ReadSubList (2013/10/3 11:41:20 +08:00)
SomeTimes The Debug Log is this.

---- Assert Short Message ----

---- Assert Long Message ----

    at BufferingManager.UpdateState()  
    at BufferingManager.UnlockedReport()  
    at BufferingManager.Report(Action`2 update, Int32 size, TimeSpan timestamp)  
    at BufferingQueue.ReportEnqueue(Int32 size, TimeSpan timestamp)  
    at StreamBuffer.ReportEnqueue(Int32 packetCount, TimeSpan timestamp)  
    at StreamBuffer.Enqueue(TsPesPacket packet)  
    at <>c__DisplayClassb.<CreatePacketHandler>b__9(TsPesPacket packet)  
    at AacStreamHandler.PacketHandler(TsPesPacket packet)  
    at TsPacketizedElementaryStream.ParseNormalPesPacket()  
    at TsPacketizedElementaryStream.ParsePesPacket()  
    at TsPacketizedElementaryStream.Flush()  
    at TsPacketizedElementaryStream.Add(TsPacket packet)  
    at TsDecoder.ParsePacket(Byte[] buffer, Int32 offset)  
    at TsDecoder.Parse(Byte[] buffer, Int32 offset, Int32 length)  
    at TsMediaParser.ProcessData(Byte[] buffer, Int32 length)  
    at <>c__DisplayClass24.<CreateReaderPipeline>b__1a(WorkBuffer wi)  
    at <WorkerAsync>d__9.MoveNext()  
    at AsyncMethodBuilderCore.Start(TStateMachine& stateMachine)  
    at AsyncTaskMethodBuilder`1.Start(TStateMachine& stateMachine)  
    at AsyncTaskMethodBuilder.Start(TStateMachine& stateMachine)  
    at QueueWorker`1.WorkerAsync()  
    at Task`1.InnerInvoke()  
    at Task.Execute()  
    at Task.ExecutionContextCallback(Object obj)  
    at ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)  
    at ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)  
    at Task.ExecuteWithThreadLocal(Task& currentTaskSlot)  
    at Task.ExecuteEntry(Boolean bPreventDoubleExecution)  
    at Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()  
    at ThreadPoolWorkQueue.Dispatch()  
    at _ThreadPoolWaitCallback.PerformWaitCallback()  
Oct 3, 2013 at 7:48 AM
The Audio and Video exhaustion messages suggest that it is not downloading segments fast enough (or at all). Fiddler can be really useful for seeing if something odd is happening on the network side of things.

If there is a step in the timestamps between segments, then that assertion could be hit. Do you know if there are any #EXT-X-DISCONTINUITY tags in the stream you are trying to play? If there is trouble downloading segments, then a skipped segment could also cause a problem.

If you can provide a link to the live stream you are trying to play in a private message, then I could take a look.
Oct 3, 2013 at 9:44 AM
Oct 14, 2013 at 7:03 PM
Did the private message I sent make sense? (Short version: There were some odd discontinuities in the live stream.)
Oct 15, 2013 at 3:01 AM
henric wrote:
Did the private message I sent make sense? (Short version: There were some odd discontinuities in the live stream.)
Yes I replay that message. Did you receive my email?
The message content is here:

Thanks for replay.
I set the project Release mode .That M3u8 file now can playback. But it's timeline display wrong and Fast-forward doesn't work.The first part of the time is not counted.
And High-definition video playback time will stuck,also the first part no problem and the second is stuck.
Best Regards

And Now I make sure the stuck problem is the buffer . When the video is stuck ,I pause it for a lit while ,then it can playback . The buffer is not enough to playback smooth . Can I manually change the size of the buffer ?
Oct 17, 2013 at 5:50 AM
For now, changing the buffering requires mucking with the source. The SM.Media.BufferingManager class has a number of constants at the top of the file. They all start with "BufferSize" or "BufferDuration". They can be changed, but should be kept in the same order (i.e., if constant A is greater than B before you change something, then constant A should still be greater than B after you change them).

However, if the problem is still that there are discontinuities in the stream as I noted in my Oct 4 email:
... the segment 1380892920.ts has 5 seconds worth of data from 11:02:53.046244 to 11:02:58.017244. The next segment, 1380892925.ts, has 10 seconds worth of data from 10:46:20.383244 to 10:46:25.357244. I downloaded both segments again from a the command line (using "fetch"). The 1380892920.ts files were identical, but the 1380892925.ts files were different. The new 1380892925.ts was smaller (739968 vs 1074420 bytes) and ran from 10:34:03.707244 to 10:34:13.736244. Neither of the two 1380892925.ts files fit into the playback sequence (I played the .ts files in Windows Media Player) and the playlist said the file should be 5 seconds long.
then at best, the extra buffering could help hide the problem as the MediaElement reads and discards the discontinuous segment--as long as the step is backwards in time; a forward step would likely cause more trouble. The real problem remains the discontinuity in the stream (coupled with the code not hiding such stream problems from MediaElement). I split much of the timestamp offset handling into a dedicated class in 8417e31589db, but I have not added any code to mitigate stream discontinuities.
Oct 17, 2013 at 12:47 PM
Oh I think I've got it
Now, in my country network conditions is very bad.Different users have different network conditions. Maybe In your country the network is so fast .
I want give them a hint and stop playing when video is buffering. Wait until the buffer more than enough time and then play video.Can I get this BUFFER state in the plugin?I use plug-ins to build projects. Similar as SamplePlayer.WP8 .
And play the Local M3U8 File is the case . You can see the M3U8 I message to you . The M3U8 File have some Videos .I download videos and want to play them by a M3U8 file. DId you have any idea for this.
Oct 18, 2013 at 12:44 PM
The buffering code is already supposed to do this. It should be trying to keep about 8 seconds ahead and once it runs dry it should not be restarting playback until it has 9 seconds buffered (it also has a memory limit of 8MB to prevent memory exhaustion). However, from looking at some issues with another stream, the buffering code appears to be having some problems.

Even if it were working as intended, there is still some scope for tuning the constants. This should be brought out to some sort of easily customizable policy object and should probably take into account the stream bandwidth and the current network conditions. It is on my TODO list...

Unfortunately, the BufferingManager is buried deep inside the code. Theoretically, one should be able to create another class that implements IBufferingManager, but the BufferingManager is being explicitly instantiated in TsMediaManager.CreateReaderPipeline().

If you can't get a hold of VS2012 Pro or VS2013 Pro, then you might try doing what I did for VS2013 for Windows 8.1. Open the s solution in VS2013 and create two new Windows Phone 8 libraries: SM.Media.WP8 and SM.TsParser.WP8. Open the SM.Media.Win81.csproj and SM.TsParser.Win81.csproj files as text or XML and copy the whole <ItemGroup> containing the <Compile> nodes from the "*.Win81.csproj" files to the corresponding "*.WP8.csproj" files (note that the location in the directory hierarchy for the new project directories needs to be the same since there are relative paths in those <Compile> nodes). Use NuGet to add Microsoft.Net.Http to SM.Media.WP8. Add a reference to SM.TsParser.WP8 in the SM.Media.WP8. I think that should let you compile with VS2013 Express for Windows Phone. If you need WP7, then I think you should be able to do the same (you just need to stick to VS2012).
Oct 18, 2013 at 1:47 PM
Oh . but now the video still be playbacking and the MediaElementState is Playing and it is dropped frames. Maybe my network is not fast. I hope to prevent this situation to stop playback. So what Can I get this BUFFER state in the plugin? when this plugin buffering I hint user in ui. I use the Microsoft Player Framework. I am using the VS2012 Pro .
I will try it again.
Oct 21, 2013 at 2:59 PM
Edited Oct 21, 2013 at 3:00 PM
If you want to get direct access to the buffering status, look for the line,
Debug.WriteLine("Sample {0} buffering {1:F2}%", mediaStreamDescription.Type, progress * 100);
in TsMediaStreamSource.cs. The code calls "ReportGetSampeProgress()", which tells MediaElement that things are buffering. This is supposed to change the state of the MediaElement, but that appears to be broken on WP8. You could add a call to something in your own code to report this value directly.

I checked in a change to the buffering logic. Could you see if this improves things for you?

Oct 22, 2013 at 3:30 AM
Thanks very much. This change improve a lot on my project .

' but that appears to be broken on WP8' That means I can't stop playback by behind code when buffering ?
Besides if require the use of this method of HlsVIew (example project)? But I use the SamplePlayer.WP8 of method in my project. I will try it again
Oct 22, 2013 at 12:20 PM
If you have to get around the Player Framework stuff to get at the phonesm objects, give the plugin a name:
                <smmedia:StreamingMediaPlugin x:Name="streamingMedia" />
Then you can add a method to StreamingMediaPlugin and call it from the page containing the player (MainPage.xaml.cs, in the SamplePlayer.WP8 example). That method has access to "MediaElementWrapper _mediaElement;" which should then let you do whatever you need to MediaElementWrapper (which contains, among other things, "TsMediaManager _tsMediaManager;").

For example, "_streamingMedia.EmergencyStop()" could call something in the MediaElementWrapper object that would then call "_tsMediaManager.Close()". That would shut everything down.

I think there are nicer ways of dealing with forcing a stop. It would probably involve overriding another method and/or subscribing to an event or two in the plugin. Off the top of my head, I'm not familiar enough with the guts of the Player Framework to tell you what those might be. Right now, I think the effect of a "stop" is for the Player Framework to pause playback and seek to 00:00:00, which isn't really ideal.
Oct 22, 2013 at 3:50 PM
Oh Now I try a lot but have not catch the buffering status. You say "in TsMediaStreamSource.cs." I can't get the handle event.
I want to set a TimeOut property. When buffering time is so long , then stop the video and hint user playback failure. I see the case can appear but it's the plugin initial load video. When the video is played or when entering next segment, seems to not trigger the timeout event. The Video playback will always be stuck in there.
I do not know how to trigger such the timeout event.
Oct 24, 2013 at 10:43 PM
Could you see if 7086e8db11e1 helps when trying to stop while buffering?

As for a timeout while buffering, another way you might want to handle it is to have a timer that gets reset whenever a sample is submitted to the MediaElement. It would need to be disabled while stopped or paused, but otherwise this should catch any situation where the player is unable to make constructive progress (including buffering). A not unreasonable place to put the reset for such a time would be inside "TsMediaStreamSource.StreamSampleHandler()".
Oct 25, 2013 at 4:00 PM
Thanks . I will try it again.
I hope this plugin better and better.
If I have a problem also consult your
Nov 9, 2013 at 1:48 AM
hi henric:
      the forwad step  Cause a big problem.  when user forwad step in the one segment, the segment finished playing and  media playback into next segment, the next segment will auto setp in the last segment position ( have  the same  timeline). The M3u8 file have  some  #EXT-X-DISCONTINUITY tags.  The above situation occur in the #EXT-X-DISCONTINUITY tag Switching .
Nov 10, 2013 at 3:09 AM
Edited Nov 10, 2013 at 11:19 PM
Hi henric the m3u8 file stream is non-live. And I can get Each segment duration(Before playing) and the number of #EXT-X-DISCONTINUITY, how can I add the TimeElapse duration and correct this. Because the TimeElapse show one segment’s TimeElapse not the total m3u8 playback stream. Can you any idea for this? Once again thank you for your work
Nov 12, 2013 at 12:02 AM
The fundamental problem is that the code that sees the #EXT-X-DISCONTINUITY and realizes something needs to happen has no way of communicating this to the bit that can do something constructive about it (the bit that feeds data to the MediaElement). This is the main reason why I still keep the project as "Alpha" instead of "Beta".

Some smarts on the part of the timestamp offset handling (SM.Media.TsTimestamp) may be sufficient for you. An implementation of the "SM.Media.ITsTimestamp" could look for large jumps in the timestamp that are not part of a seek and change them into small jumps (say 200ms or so?). I've been meaning to look at this for some time...
Dec 4, 2013 at 7:51 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.