Delayed stream: Using FMS for semi-live broadcasting

When you watch a live show on TV, its never really live. Its not only the foregone latency made by the different machines - but a very intended delay designed to let the editor a way to stop the transmitting if they fill such need.

You can use your imagination to complete the possible scenario.

Unfortunately, Adobe's Encoder or Flash Media Server don't have live stream delay feature out of the box. So, if you want to have such delay with Adobe FMS, you will need some server-side scripting. (Go ahead, fill a request to adobe's wish list)

On the other hand, this will be quite simple.

[NOTE: The code here is not real tested code.]

Step 1: Record your stream.

  • Method 1:
    You can use SSAS (Server Side ActionScript) to the live stream. Adobe's documentation is fairly descriptove: Stream.record()
    What you actually do is adding the recording functionality to the Application.onPublish event. Your final code will be similar to:


    application.onAppStart = function()
    {
     trace("Starting recoding application");
    };
     
    application.onConnect = function(clientObj)
    {
     this.acceptConnection(clientObj);
    };
     
    application.onPublish = function (clientObj , stream)
    {
     trace("Publish: "+stream.name);
     stream.record();
    };
    application.onUnpublish = function(clientObj, stream)
    {
     trace("Unpublish: "+stream.name);
     stream.record(false);
    };
    
    


    NOTE: Recording using this method will save the file with its default name:
    "When you record a stream, the server creates a file with the
    name you passed to the Stream.get() method. The
    server automatically creates a “streams” directory and subdirectories
    for each application instance name. If a stream isn’t associated
    with an application instance, it is stored in a subdirectory called “_definst_”
    (default instance). For example, a stream from the default lecture application
    instance would be stored here: applications\lectures\streams\_definst_.
    A stream from the monday lectures application instance would be
    stored here: applications\lectures\streams\monday. "
  • Method 2:
    If you are using Adobe's Flash Media Live Encoder - you can simply tick the record checkbox.
Step 2: Play it on your own time (Delayed)
Now, when you start your broadcasting - use the Stream.play() method to stream the recoded file to everyone.

The play function can link one file after another.. so we can add a preliminary video file as a place holder for the delayed time and than add the recorded file right afterward.
Doing that - we can easily use the "onPublish" event to make it all work so our code will be similar to this one:


    
application.onPublish = function (clientObj , stream)
{
 trace("Publish: "+stream.name);
 stream.record() 

stream.play("placeHolder.flv", -1, -1);

stream.play("recorded.flv", -1, -1);

};

application.onUnpublish = function(clientObj, stream)
{
 trace("Unpublish: "+stream.name);
 stream.record(false);

stream.play(false);
};



Enjoy!

WebResources - Watch the creation time

One day I had a simple task to do: take one site, fully working with no issues and load it on another server.

It sounds simple. Well, just like any real time-stealing task everything went fine till I tested it and from some odd reason some of the menus and visual components went crasy.

The problem we saw was bad request or denied access to the .axd compiled resources on page. 

After hours of investigation I found it: The servers TIME & DATE were wrong. When you upload a compiled resource - the server use the creation date to create a key for that resource. That key probably being computed using the current date and file's creation date. When the creation date is in the future - the calculation returns some invalid value and that resource is being denied.

I hope I saved someone's time..

Better thumbnail creation then "GetThumbnailImage" in C sharp

In many cases you may want to create a smaller version of your images. maybe for mobile devices or for images view on your page.

One known way is to use the "ready-made" GetThumbnailImage function. That will generally do the job (another way is below):


int Width;
int Height;
int ThumbWidth;
int ThumbHeight;
string SavePath = "[Some save path"];
string FilenameWOext = "[]";
string FileExtention;

Bitmap btmp1 = new Bitmap(SavePath + FilenameWOext + FileExtention); 

Width = btmp1.Width;
Height = btmp1.Height;

System.Drawing.Image.GetThumbnailImageAbort myCallBack =
new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback);
         
if (ThumbWidth > Width && ThumbHeight > Height)
{
      ThumbWidth = Width;
      ThumbHeight = Height;
}

System.Drawing.Image myThumbnail1 = btmp1.GetThumbnailImage(ThumbWidth,
ThumbHeight, myCallBack, IntPtr.Zero);
myThumbnail1.Save(SavePath + ThumbFile);

btmp1.Dispose();

HOWEVER, this will sometimes wont work. when using images from some digital cameras, there is addotional EXIF data that makes the generated image to be blur.
Fortunatly, there is another way using Graphics.DrawImage


Bitmap resized = new Bitmap(ThumbWidth, ThumbHeight);
            Graphics g = Graphics.FromImage(resized);
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            g.DrawImage(btmp1, new Rectangle(0, 0, resized.Width, resized.Height));

g.Dispose();


This way will work with EXIF images or any other image with high quality too!

Enjoy!

Adobe P2P VideoPhone with asp.net registration

The Adobe p2p and Stratus are not that new. its sample application is cool and shows more than just textual p2p connection like other tutorials, but actually shows a good enough voice and image transfer.


BUT, as a .Net programmer, the registration Python code was a bit wierd to work with. Moreover, the application is literally poor in user management, so I needed a good start point to have that registration use real DB.


So, you want to use ASP.NET with Adobe Stratus VideoPhone ? here you go: ( I used Linq to SQL for  this one. DB name is simply "P2P")
reg.asxh:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace P2PRegistration
{
    /// 
    /// Summary description for $codebehindclassname$
    /// 
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class reg : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            RegistrationDBDataContext db = new RegistrationDBDataContext();

            string user = context.Request.QueryString["username"];
            string identity = context.Request.QueryString["identity"];
            string friendsString = context.Request.QueryString["friends"];

            List friends = new List();

            if (friendsString != null && friendsString != "")
            {
                friends = friendsString.Split(',').ToList();
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write(@"");
            context.Response.Write(@"");

            if (user != null && user != "" && identity != null && identity != "")
            {
                context.Response.Write(@"");
                try
                {
                    var rUser = from p in db.registrations where (p.m_username == user) select p;
                    if (rUser.Count() > 0)
                    {
                        //update
                        rUser.First().m_username = user;
                        rUser.First().m_identity = identity;
                        rUser.First().m_updatetime = DateTime.Now;
                        
                        
                    }
                    else
                    {
                        registration newReg = new registration();
                        newReg.m_username = user;
                        newReg.m_identity = identity;
                        newReg.m_updatetime = DateTime.Now;

                        db.registrations.InsertOnSubmit(newReg);
                    }

                    db.SubmitChanges();

                    context.Response.Write("true");
                }
                catch
                {
                    context.Response.Write("false");
                }
                context.Response.Write(@"");
            }

            foreach (string f in friends)
            {
                context.Response.Write(@"");

                context.Response.Write(@"");
                context.Response.Write(context.Server.UrlEncode(f));
                context.Response.Write(@"");

                
                var rFriends = from p in db.registrations where p.m_username == f && p.m_updatetime > DateTime.Now.AddHours(-1) select p;
                foreach (registration r in rFriends)
                {
                    string ident = r.m_identity;
                    if (ident == null) ident = "";

                    context.Response.Write(@""+context.Server.UrlEncode(ident)+@"");

                    if (f != r.m_username)
                    {
                        context.Response.Write(@""+r.m_username+"");
                    }
                }

                context.Response.Write(@"");
            }

            context.Response.Write(@"");
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}


You will need a simple  DB for that:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[registrations]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[registrations](
    [m_username] [varchar](100) NOT NULL,
    [m_identity] [varchar](100) NULL,
    [m_updatetime] [datetime] NULL,
 CONSTRAINT [PK__registrations__7C8480AE] PRIMARY KEY CLUSTERED 
(
    [m_username] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END

Now, just place the url of your reg.aspx in the videolab source mxml:

...
    // please insert your webservice URL here for exchanging
    private const WebServiceUrl:String = "http://[your domain here]/reg.ashx"; 
...

Enjoy!

allowFullScreen is not working in IE but do work in FF

This can happen from several reasons, but the key concept is to remember that IE and FF read the parameters differently and that FF read the tag and IE uses the tag.

Now try the following:
1. Note that the parameters should not have any whitespace in it. FF will work with it but IE will not.
2. the allowFullScreen must be set to true (see if this is being done in the generated html when using JS helpers)
3. Using swfObject - try to set the minimum version to 9.0.115,0

If it worked for you or you had another experiance - tell me.
Thanks;
David