NextPVR Forums
  • ______
  • Home
  • New Posts
  • Wiki
  • Members
  • Help
  • Search
  • Register
  • Login
  • Home
  • Wiki
  • Members
  • Help
  • Search
NextPVR Forums Public Developers v
« Previous 1 … 8 9 10 11 12 … 93 Next »
Streaming media

 
  • 0 Vote(s) - 0 Average
Streaming media
imilne
Offline

Posting Freak

Posts: 2,423
Threads: 135
Joined: Feb 2008
#21
2012-12-08, 10:10 AM
Yes that does seem to help. Cheers for that. Most of their URLs that end _a720p.mrv can be changed to _h720.mov as well but that didn't seem to make any difference. It might just be the same file with a different extension rather than a different container, I haven't checked yet. Interestingly, the .mov trick can be used to get 1080p versions but they don't provide an rss feed for those ones.

Because of the problems I went back to experimenting with sub's web proxy idea and got it working a lot better last night. For some reason there's a slight delay on playback of the first trailer selected but after that all subsequent ones start streaming almost instantly. They get the correct timeline details as well unlike with a growing file. This is a super-simple proxy that handles the download (with correct user-agent) and just feeds the data into a http://localhost/whatever address for the PlayVideoFile() method to work from.

I don't know if that'll work with non-PC clients though, and it's not something I can test. I could post/email the plugin as-is just now if anyone wanted to test that.

I still haven't decided how to handle buffering of videos. I'll certainly add an option to pre-download trailers as a background task, but some kind of buffering for stuff that hasn't been downloaded may still be needed for those on slower connections (even with the 480p streams). My "problem" (Rolleyes) is that I have a 60meg connection so it's difficult to test stuff like that.

Oh, and the 120-odd trailers (at 720p) available from the feed just now total just under 10 gigabytes.

Iain
whurlston
Offline

Posting Freak

Posts: 7,885
Threads: 102
Joined: Nov 2006
#22
2012-12-08, 06:49 PM (This post was last modified: 2012-12-09, 02:52 AM by whurlston.)
You shouldn't need to use the proxy now that ffmpeg supports user-agent. sub suggested that as a work around for the user agent issue. You should be able to just call PlayVideoFile([ffmpeg output filename]).

If you want to start playing the file before it's finished downloading, you can use HTTPRequest and HTTPResponse and pipe the stream to the ffmpeg input. That way you can monitor how much has been downloaded and launch playback at any point you want. I haven't completed the plugin agnostic code yet but here is a modified version for you. I made some edits in Notepad because my dev OS partition is corrupted so there may be some compile problems but you should be able to fix them.

Code:
public class ffmpeg
{
  private System.Net.HttpWebRequest _Request;
  private System.Net.HttpWebResponse _Response;
  private long expected = 0;

  public string Url { get; set; }
  public string File { get; set; }
  public string ffmpegPath { get; set; }
  private const string ffmpegCommandLine "-c copy -f mpegts -bsf h264_mp4toannexb"
  public int MuxSize = 5000000;
  public int BufferKB { get; set; }
  public Hashtable NotifyArgs = new Hashtable();
        public bool isWatchable = false;

  public delegate void ErrorEventHandler(object sender, Error e);
  public delegate void StatusChangeEventHandler(object sender, StatusChange e);
  public delegate void ProgressChangeEventHandler(object sender, double percentComplete);

  public event ErrorEventHandler ErrorEvent;
  internal virtual void RaiseOnError(object sender, Error e)
  {
   if (this.ErrorEvent != null) { this.ErrorEvent(sender, e); }
  }

  public event StatusChangeEventHandler StatusChangeEvent;
  internal virtual void RaiseOnStatusChange(object sender, StatusChange e)
  {
   if (this.StatusChangeEvent != null) { this.StatusChangeEvent(sender, e); }
  }

  public event ProgressChangeEventHandler ProgressChangeEvent;
  internal virtual void RaiseOnProgressChange(object sender, double p)
  {
   if (this.ProgressChangeEvent != null) { this.ProgressChangeEvent(sender, p); }
  }
  
  public ffmpeg(Hashtable args, string url, string ffmpegPath, int bufferSize, string bufferPath, string fileName)
  {
   this.Url = url;
   this.ffmpegPath = ffmpegPath;
   if (!System.IO.File.Exists(ffmpegPath)) throw new FileNotFoundException("File not found.", ffmpegPath);
   this.File = Path.Combine(bufferPath, fileName);
   if (!Directory.Exists(bufferPath)) Directory.CreateDirectory(bufferPath);
   this.BufferKB = bufferSize;
  }

  public void Start()
  {
   System.Threading.Thread dlMovie = new System.Threading.Thread(this.DownloadVideo);
   dlMovie.IsBackground = true;
   dlMovie.Start();
  }

  private void DownloadVideo()
  {
   if (System.IO.File.Exists(this.File))
   {
    this.isWatchable = true;
    RaiseOnStatusChange(this, new StatusChange(StatusType.ReadyToWatch, this.File));
    this.NotifyArgs["@streamUrl"] = this.File;
    EventBus.GetInstance().Notify("APPLE_TRAILERS_PLAY_CALLBACK", this.NotifyArgs);
    return;
   }
  
   _Request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(this.Url);
   _Request.CachePolicy = new System.Net.Cache.HttpRequestCachePolicy(System.Net.Cache.HttpRequestCacheLevel.CacheIfAvailable);
   _Request.UserAgent = "QuickTime";
   _Request.KeepAlive = false;
   _Request.Timeout = 5000;
   // This needs to be set or the first call takes a few seconds.
   _Request.Proxy = null;
   _Request.ServicePoint.ConnectionLeaseTimeout = 5000;
   _Request.ServicePoint.MaxIdleTime = 5000;

   _Response = (System.Net.HttpWebResponse)_Request.GetResponse();
   Logger.Debug(string.Format("Apple Trailers: Response: {0}: {1}", _Response.StatusCode, _Response.StatusDescription));

   string[] contentLength = _Response.Headers.GetValues("Content-Length");
   expected = long.Parse(contentLength[0]);
   string message = string.Format("Apple Trailers: Starting download of \"{0}\" to \"{1}\". Expected length is {2}", this.Url, this.File, expected);
   Logger.Debug(message);
   Process ffmpegProcess = new Process();
   ffmpegProcess.StartInfo.FileName = ffmpegPath;
   ffmpegProcess.StartInfo.Arguments = String.Format("-i - {0} -muxsize {1} \"{2}\"", this.ffmpegCommandLine, this.MuxSize, this.File);
   ffmpegProcess.StartInfo.CreateNoWindow = true;
   ffmpegProcess.StartInfo.UseShellExecute = false;
   ffmpegProcess.StartInfo.RedirectStandardInput = true;
   ffmpegProcess.Start();

   if (!ffmpegProcess.HasExited)
   {
    ffmpegProcess.StandardInput.AutoFlush = false;
    ffmpegProcess.Refresh();
    using (Stream webresponse = _Response.GetResponseStream())
    {
     using (Stream ffmpegInput = ffmpegProcess.StandardInput.BaseStream)
     {
      byte[] buffer = new byte[1024];
      int blockSize;
      int bytesDownloaded = 0;
      try
      {
       while (bytesDownloaded < expected && (blockSize = webresponse.Read(buffer, 0, buffer.Length)) > 0)
       {
        ffmpegInput.Write(buffer, 0, blockSize);
        ffmpegInput.Flush();
        bytesDownloaded += blockSize;
        if (this.expected - bytesDownloaded < buffer.Length) buffer = new byte[this.expected - bytesDownloaded];
        // Check if we have downloaded enough bytes for the buffer.
        // No point in doing a filesystem call to check the filesize if we haven't.
        if (!this.isWatchable && bytesDownloaded > this.BufferKB * 1024)
        {
         // Check if ffmpeg has written enough bytes for the buffer.
         if (System.IO.File.Exists(File) && new System.IO.FileInfo(File).Length > this.BufferKB * 1024)
         {
          long fileSize = new System.IO.FileInfo(File).Length;
          Logger.Debug(string.Format("Apple Trailers: Setting video as watchable... {0} of {1} downloaded. {2} written to disk.", bytesDownloaded, expected, fileSize));
          this.isWatchable = true;
          RaiseOnStatusChange(this, new StatusChange(StatusType.ReadyToWatch, this.File));
          this.NotifyArgs["@streamUrl"] = this.File;
          EventBus.GetInstance().Notify("APPLE_TRAILERS_PLAY_CALLBACK", this.NotifyArgs);
         }
        }
       }
                            
       // The file was not as large as the buffer setting so mark it as watchable once completed.
       if (!this.isWatchable)
       {
        Logger.Debug("Apple Trailers: Setting video as watchable because download completed...");
        this.isWatchable = true;
        RaiseOnStatusChange(this, new StatusChange(StatusType.ReadyToWatch, this.File));
        this.NotifyArgs["@streamUrl"] = this.File;
        EventBus.GetInstance().Notify("APPLE_TRAILERS_PLAY_CALLBACK", this.NotifyArgs);
       }
       Logger.Debug(string.Format("Apple Trailers: Downloaded {0} of {1}.", bytesDownloaded, expected));
      }
      catch (Exception eX)
      {
       if (this.NotifyArgs.ContainsKey("@errorMessage")) this.NotifyArgs["@errorMessage"] = "webrequest failed";
       this.NotifyArgs.Add("@errorMessage", "webrequest failed");
       this.NotifyArgs.Add("@callerPlugin", "ffmpeg");
       EventBus.GetInstance().Notify("APPLE_TRAILERS_PLAY_CALLBACK_ERROR", this.NotifyArgs);
       Logger.Debug(eX.Message + "\r\n" + eX.StackTrace);
       Logger.Debug(string.Format("Apple Trailers: Downloaded {0} of {1} before error.", bytesDownloaded, expected));
      }
     }
    }
   }
   else
   {
    if (this.NotifyArgs.ContainsKey("@errorMessage")) this.NotifyArgs["@errorMessage"] = "ffmpeg exited prematurely";
    this.NotifyArgs.Add("@errorMessage", "ffmpeg exited prematurely");
    this.NotifyArgs.Add("@callerPlugin", "ffmpeg");
    EventBus.GetInstance().Notify("APPLE_TRAILERS_PLUGIN_PLAY_CALLBACK_ERROR", this.NotifyArgs);
   }
   if (!ffmpegProcess.HasExited)
   {
    Logger.Debug("Apple Trailers: Forcing Close() on ffmpeg process...");
    ffmpegProcess.Close();
   }
   else
   {
    Logger.Debug("Apple Trailers: ffmpeg process closed automatically...");
   }
  }
}

public enum StatusType
{
  Cancelled,
  Completed,
  ReadyToWatch,
  Resumed,
  Started
}

You will notice that I am not setting the fps. Instead I am using the -muxsize switch. It solves the same problem (with the same caveat) but is better than setting the fps. I defaulted it to 5Mb/s so that muxed filesize is roughly the same as the original on 720p streams. Adjust as needed.
whurlston
Offline

Posting Freak

Posts: 7,885
Threads: 102
Joined: Nov 2006
#23
2012-12-08, 06:49 PM (This post was last modified: 2012-12-09, 02:59 AM by whurlston.)
To use the above code:

Define two global variables in your plugin and add the following to your IEventNotification and NewStyleButtonPlugin sections:

Code:
#region global variables
private bool isPlaying = false;
private bool isWatchable = false;
#endregion

#region IEventNotification Members
public void Notify(string eventName, object eventArg)
{
  switch (eventName)
  {
  // Set variables but don't actually call PlayVideo() in case we are not the active plugin.
  case "APPLE_TRAILERS_PLUGIN_PLAY_CALLBACK":
   isWatchable = true;
   youtubeVideoArgs = (Hashtable)eventArg;
   break;
  case "APPLE_TRAILERS_PLUGIN_PLAY_CALLBACK_ERROR":
   PluginHelperFactory.GetPluginHelper().ActivatePopup(null);
   PluginHelperFactory.GetPluginHelper().ForceRender();
   PluginHelperFactory.GetPluginHelper().ShowMessage((string)((Hashtable)eventArg)["@errorMessage"]);
   break;
  case "PLAYBACK_STOPPED":
   if (downloader != null)
   downloader = null;
   isWatchable = false;
   isPlaying = false;
   break;
  }
}
#endregion

#region NewStyleButtonListPlugin Members
public override List<UiElement> GetRenderList()
{
// Play Video if one is ready.
// This will not be called if we are not the active plugin.
if (!isPlaying && isWatchable)
{
  isPlaying = true;
  PluginHelperFactory.GetPluginHelper().PlayVideoFile((string)(((Hashtable)youtubeVideoArgs)["@streamUrl"]), (Hashtable)youtubeVideoArgs);
}
List<UiElement> renderList = new List<UiElement>();
// Add stuff to renderList here if needed.
return renderList;
}
#endregion

Then add the following to your plugin:
Code:
PlayTrailer()
{
// Do whatever you do to parse the M4V url then call the downloader

ffmpeg downloader = new ffmpeg(new HashTable(), "http://trailers.apple.com/somtrailer.m4v", "c:\ffmpeg\ffmpeg.exe", 1024, "C:\Path\To\Download\Folder", "somefile.ts");
downloader.StatusChangeEvent += new ffmpeg.StatusChangeEventHandler(downloader_StatusChangeEvent);
downloader.ErrorEvent += new ffmpeg.ErrorEventHandler(downloader_ErrorEvent);
downloader.Start();
}

void downloader_StatusChangeEvent(object sender, StatusChange e)
{
Logger.Debug("Apple Trailers: Video Downloader Status Change: " + e.status.ToString() + " : " + e.message);
}

void downloader_ErrorEvent(object sender, Error e)
{
Logger.Error("Apple Trailers: Video Downloader Error: " + e.error.ToString() + " : " + e.message);
}

I'm out of time now but later I'll show you how I allow other plugins to request the videos.
imilne
Offline

Posting Freak

Posts: 2,423
Threads: 135
Joined: Feb 2008
#24
2012-12-08, 08:16 PM
I'll reply with a quick 'thanks' for now, and respond properly tomorrow...

Iain
whurlston
Offline

Posting Freak

Posts: 7,885
Threads: 102
Joined: Nov 2006
#25
2012-12-09, 02:58 AM
I edited the ffmpeg.DownloadVideo() above because I forgot to set the UserAgent:
Code:
_Request.UserAgent = "QuickTime";
My goal was to change it to use WebClient.DownloadReadAsync() or WebClient.DownloadDataAsync() because I have a class derived from WebClient that utilizes user cookies on the machine. I haven't gotten that far yet though.
imilne Wrote:I'll reply with a quick 'thanks' for now
That's all I need.
imilne
Offline

Posting Freak

Posts: 2,423
Threads: 135
Joined: Feb 2008
#26
2012-12-09, 12:54 PM
Weirdly neither the latest ffmpeg from the web nor the one you linked to will recognise the -muxsize option for me. I'm probably doing something really stupid, but I haven't sussed out what it is yet Big Grin

I've now got the proxy working near flawlessly, and it's interesting to note how similar much of it is to how your stuff works. I'd still like to try and implement your solution though, especially if it'll help with NMT clients. They wouldn't be able to handle one of these .m4v/.mov streams, is that the case?

The delay-on-first-play problem I have seems to be related to a fairly common problem with WebRequest looking for proxy information, and I note you have the =null solution to it that I've read about. Unfortunately that makes no difference for me...the best I can do for now is to have the plugin make a request (anywhere) in advance of the user trying to play a video.

The proxy feeds the data directly into PlayVideoFile() and can save it to disk at the same time. I still haven't given much thought to buffering, but I'm semi-confident something will be possible. Right now my biggest annoyance is a lack of testing capability due to the wife running the youtube plugin 24/7 on my media PC (I'd fresh installed everything some time back and never put it back on), so you've got at least one fan there...

No programming for today though...it's time to put the Christmas tree up!

Thanks again for all your help

Iain
whurlston
Offline

Posting Freak

Posts: 7,885
Threads: 102
Joined: Nov 2006
#27
2012-12-09, 05:12 PM
imilne Wrote:Weirdly neither the latest ffmpeg from the web nor the one you linked to will recognise the -muxsize option for me. I'm probably doing something really stupid, but I haven't sussed out what it is yet Big Grin
Probably because all ffmpeg builds are looking for "-muxrate" instead of "-muxsize". :o

imilne Wrote:I've now got the proxy working near flawlessly, and it's interesting to note how similar much of it is to how your stuff works. I'd still like to try and implement your solution though, especially if it'll help with NMT clients. They wouldn't be able to handle one of these .m4v/.mov streams, is that the case?
The NMT will actually play YouTube mp4 and flv streams but it drops out of the NextPVR interface to the native player (Mono?) to do it. By remuxing to .ts they play inside NextPVR so we get to keep overlays. I haven't tested Apple m4v files though. I'll put it on my todo list.

imilne Wrote:Right now my biggest annoyance is a lack of testing capability due to the wife running the youtube plugin 24/7 on my media PC
Big Grin
mvallevand
Offline

Posting Freak

Ontario Canada
Posts: 52,767
Threads: 954
Joined: May 2006
#28
2012-12-11, 01:25 AM
The NMTcan't play proprietary flv format at all, but if you pass the http url they might play .mov if they can be served from the apple host fast enough. 720p from Apple should be easy for most people possibly 1080p with good conditions.

Martin
whurlston
Offline

Posting Freak

Posts: 7,885
Threads: 102
Joined: Nov 2006
#29
2012-12-11, 04:08 AM
mvallevand Wrote:The NMTcan't play proprietary flv format at all
Apple doesn't use flv. I was talking about Youtube flv streams. The NMT will actually play them (I was parsing the url wrong before which was why I couldn't get them to play).
mvallevand
Offline

Posting Freak

Ontario Canada
Posts: 52,767
Threads: 954
Joined: May 2006
#30
2012-12-11, 04:17 AM
I am surprised because AFAIK the NMT (Sigma) doesn't support the FLV codec or WebM. Perhaps you were grabbing a 3GPP file and not MP4?

Martin
« Next Oldest | Next Newest »

Users browsing this thread: 1 Guest(s)

Pages (5): « Previous 1 2 3 4 5 Next »


Possibly Related Threads…
Thread Author Replies Views Last Post
  Video streaming URL and parameters? cncb 1 1,758 2021-10-22, 06:58 PM
Last Post: sub
  Looking for C# UPnP Media Server code bgowland 5 7,556 2016-12-16, 08:25 PM
Last Post: mvallevand
  Media Browser Integration lukemb3 557 148,269 2015-11-29, 05:27 PM
Last Post: UncleJohnsBand
  Media\Show Directory mvallevand 12 6,004 2014-07-02, 10:58 AM
Last Post: sub
  Advice on which streaming method to use fred250 17 6,156 2013-09-14, 11:14 AM
Last Post: fred250
  Media\Shows Metadata mvallevand 2 1,828 2013-05-22, 04:09 AM
Last Post: mvallevand
  On-the-fly transcoding for streaming recordings / videos bgowland 6 3,982 2012-06-03, 03:10 AM
Last Post: bgowland
  Building a list of files in media folders (npvr Music and Videos) bgowland 2 1,847 2012-02-05, 10:29 AM
Last Post: bgowland
  NPVR HTTP streaming bug? tmrt 3 1,819 2010-12-28, 11:48 PM
Last Post: tmrt
  Windows Media Connect - Storing stuff on a Home Server psycik 3 2,636 2009-09-29, 03:47 AM
Last Post: mvallevand

  • View a Printable Version
  • Subscribe to this thread
Forum Jump:

© Designed by D&D, modified by NextPVR - Powered by MyBB

Linear Mode
Threaded Mode