How to control the youtube flash player with c#?

2019-03-20 12:22发布

My goal is to make a open source YouTube player that can be controlled via global media keys. The global key issue I got it covered but the communication between the YouTube player and my Windows Forms application just doesn't work for some reason.

So far this is what I have:

private AxShockwaveFlashObjects.AxShockwaveFlash player;
player.movie = "http://youtube.googleapis.com/v/9bZkp7q19f0"
...
private void playBtn_Click(object sender, EventArgs e)
{
    player.CallFunction("<invoke name=\"playVideo\" returntype=\"xml\"></invoke>");
}

Unfortunately this returns:

"Error HRESULT E_FAIL has been returned from a call to a COM component."

What am I missing? Should I load a different URL?
The documentation states that YouTube player uses ExternalInterface class to control it from JavaScript or AS3 so it should work with c#.


UPDATED:


Method used to embed the player: http://www.youtube.com/watch?v=kg-z8JfOIKw

Also tried to use the JavaScript-API in the WebBrowser control but no luck (player just didn't respond to JavaScript commands, tried even to set WebBrowser.url to a working demo, all that I succeeded is to get the onYouTubePlayerReady() to fire using the simple embedded object version )

I think there might be some security issues that I'm overseeing, don't know.


UPDATE 2:


fond solution, see my answer below.

4条回答
forever°为你锁心
2楼-- · 2019-03-20 12:54

It sounds like your trying to use Adobe Flash as your interface; then pass certain variables back into C#.

An example would be this:

In Flash; create a button... Actionscript:

on (press) {
    fscommand("Yo","dude");
}

Then Visual Studio you just need to add the COM object reference: Shockwave Flash Object

Then set the embed to true;

Then inside Visual Studio you should be able to go to Properties; find fscommand. The fscommand will allow you to physically connect the value from the Flash movie.

AxShockwaveFlashObjects._IShockwaveFlashEvents_FSCommandEvent 

That collects; then just use e.command and e.arg for example to have the collected item do something.

Then add this to the EventHandler;

lbl_Result.Text="The "+e.args.ToString()+" "+e.command.ToString()+" was clicked";

And boom it's transmitting it's data from Flash into Visual Studio. No need for any crazy difficult sockets.

On a side note; if you have Flash inside Visual Studio the key is to ensure it's "embed is set to true." That will hold all the path references within the Flash Object; to avoid any miscalling to incorrect paths.

I'm not sure if that is the answer your seeking; or answers your question. But without more details on your goal / error. I can't assist you.

Hope this helps. The first portion should actually show you the best way to embed your Shockwave into Visual Studio.

Make sure you add the correct reference:

  1. Inside your project open 'Solution Explorer'
  2. Right-Click to 'Add Reference'
  3. Go to 'COM Object'

Find Proper object;

COM Objects:
Shockwave ActiveX
Flash Accessibility
Flash Broker
Shockwave Flash

Hope that helps.

It sounds like you aren't embedding it correctly; so you can make the call to it. If I'm slightly mistaken; or is this what you meant:

If your having difficulty Ryk had a post awhile back; with a method to embed YouTube videos:

<% MyYoutubeUtils.ShowEmebddedVideo("<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/gtNlQodFMi8&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/gtNlQodFMi8&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="425" height="344"></embed></object>") %>

Or...

public static string ShowEmbeddedVideo(string youtubeObject)
{
    var xdoc = XDocument.Parse(youtubeObject);
    var returnObject = string.Format("<object type=\"{0}\" data=\{1}\"><param name=\"movie\" value=\"{1}\" />",
        xdoc.Root.Element("embed").Attribute("type").Value,
        xdoc.Root.Element("embed").Attribute("src").Value);
    return returnObject;
}

Which you can find the thread here: https://stackoverflow.com/questions/2547101/purify-embedding-youtube-videos-method-in-c-sharp

I do apologize if my post appears fragmented; but I couldn't tell if it was the reference, the variable, the method, or embed that was causing you difficulties. Truly hope this helps; or give me more details and I'll tweak my response accordingly.


C# to ActionScript Communication:

import flash.external.ExternalInterface;
ExternalInterface.addCallback("loadAndPlayVideo", null, loadAndPlayVideo);
function loadAndPlayVideo(uri:String):void
{
       videoPlayer.contentPath = uri;
}

Then in C#; add an instance of the ActiveX control and add the content into a Constructor.

private AxShockwaveFlash flashPlayer;
public FLVPlayer ()
{

      // Add Error Handling; to condense I left out.
      flashPlayer.LoadMovie(0, Application.StartupPath + "\\player.swf");
}

fileDialog = new OpenFileDialog();
fileDialog.Filter = "*.flv|*.flv";
fileDialog.Title = "Select a Flash Video File...";
fileDialog.Multiselect = false;
fileDialog.RestoreDirectory = true;

if (fileDialog.ShowDialog() == DialogResult.OK)
{
     flashPlayer.CallFunction("<invoke" + " name=\"loadAndPlayVideo\" returntype=\"xml">       <arguements><string>" + fileDialog.FileName + "</string></arguements></invoke>");
}

ActionScript Communication to C#:

import flash.external.ExternalInterface;
ExternalInterface.call("ResizePlayer", videoPlayer.metadata.width, videoPlayer.metadata.height);

flashPlayer.FlashCall += new _IShockwaveFlashEvents_FlashCallEventHandler(flashPlayer_FlashCall);

Then the XML should appear:

<invoke name="ResizePlayer" returntype="xml">
     <arguements>
            <number> 320 </number>
            <number> 240 </number>
     </arguments>
</invoke>

Then parse the XML in the event handler and invoke the C# function locally.

 XmlDocument document = new XmlDocument();
    document.LoadXML(e.request);
    XmlNodeList list = document.GetElementsByTagName("arguements");
    ResizePlayer(Convert.ToInt32(list[0].FirstChild.InnerText),   Convert.ToInt32(list[0].ChildNodes[1].InnerText));

Now they are both passing data back and forth. That is a basic example; but by utilizing the ActionScript Communication you shouldn't have any issues utilizing the native API.

Hope that is more helpful. You can expand on that idea by a utility class for reuse. Obviously the above code has some limitations; but hopefully it points you in the right direction. Was that direction you were attempting to go? Or did I still miss the point?


Create a new Flash Movie; in ActionScript 3. Then on the initial first frame; apply the below:

Security.allowDomain("www.youtube.com");
var my_player:Object;
var my_loader:Loader = new Loader();

my_loader.load(new URLRequest("http://www.youtube.com/apiplayer?version=3"))
my_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);

function onLoaderInit(e:Event):void{
addChild(my_loader);
my_player = my_loader.content;
my_player.addEventListener("onReady", onPlayerReady); 
} 

function onPlayerReady(e:Event):void{
my_player.setSize(640,360);
my_player.loadVideoById("_OBlgSz8sSM",0);
} 

So what exactly is that script doing? It is utilizing the native API and using ActionScript Communication. So below I'll break down each line.

Security.allowDomain("www.youtube.com");

Without that line YouTube won't interact with the object.

var my_player:Object;

You can't just load a movie into the movie; so we will create a variable Object. You have to load a special .swf that will contain access to those codes. The below; does just that. So you can access the API.

var my_loader:Loader = new Loader();
my_loader.load(new URLRequest("http://www.youtube.com/apiplayer?version=3")); 

We now reference the Google API per their documentation.

my_loader.contentLoaderInfo.addEventListener(Event.INIT, onLoaderInit);

But in order to actually work with our object; we need to wait for it to be fully initialized. So the Event Listener will wait; so we know when we can pass commands to it.

The onLoaderInit function will be triggered upon initialization. Then it's first task will be my_loader to display the list so that the video appears.

The addChild(my_loader); is what will load one; the my_player = my_loader.content; will store a reference for easy access to the object.

Though it has been initialized; you have to wait even further... You use my_player.addEventListener("onReady", onPlayerReady); to wait and listen for those custom events. Which will allow a later function to handle.

Now the player is ready for basic configuration;

function onPlayerReady(e:Event):void{
my_player.setSize(640,360);
} 

The above function starts very basic manipulation. Then the last line my_player.loadVideoById("_OBlgSz8sSM",0); is referencing the particular video.

Then on your stage; you could create two buttons and apply:

play_btn.addEventListener(MouseEvent.CLICK, playVid); 
function playVid(e:MouseEvent):void { 
my_player.playVideo(); 
} 
pause_btn.addEventListener(MouseEvent.CLICK, pauseVid); 
function pauseVid(e:MouseEvent):void { 
my_player.pauseVideo();
}

Which would give you a play and pause functionality. Some additional items you could use our:

loadVideoById() 
cueVideoById() 
playVideo() 
pauseVideo() 
stopVideo() 
mute()
unMute()

Keep in mind those can't be used or called until it has been fully initialized. But using that; with the earlier method should allow you to layout the goal and actually pass variables between the two for manipulation.

Hopefully that helps.

查看更多
仙女界的扛把子
3楼-- · 2019-03-20 12:58

After a lot of tries and head-hammering, I've found a solution:

Seems that the Error HRESULT E_FAIL... happens when the flash dosen't understand the requested flash call. Also for the youtube external api to work, the js api needs to be enabled:

player.movie = "http://www.youtube.com/v/VIDEO_ID?version=3&enablejsapi=1"

As I said in the question the whole program is open source, so you will find the full code at bitbucket.
Any advice, suggestions or collaborators are highly appreciated.

The complete solution:

Here is the complete guide for embedding and interacting with the YouTube player or any other flash object.

After following the video tutorial , set the flash player's FlashCall event to the function that will handle the flash->c# interaction (in my example it's YTplayer_FlashCall )

the generated `InitializeComponent()` should be:

...
this.YTplayer = new AxShockwaveFlashObjects.AxShockwaveFlash();
this.YTplayer.Name = "YTplayer";
this.YTplayer.Enabled = true;
this.YTplayer.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("YTplayer.OcxState")));
this.YTplayer.FlashCall += new AxShockwaveFlashObjects._IShockwaveFlashEvents_FlashCallEventHandler(this.YTplayer_FlashCall);
...

the FlashCall event handler

private void YTplayer_FlashCall(object sender, AxShockwaveFlashObjects._IShockwaveFlashEvents_FlashCallEvent e)
{
    Console.Write("YTplayer_FlashCall: raw: "+e.request.ToString()+"\r\n");
    // message is in xml format so we need to parse it
    XmlDocument document = new XmlDocument();
    document.LoadXml(e.request);
    // get attributes to see which command flash is trying to call
    XmlAttributeCollection attributes = document.FirstChild.Attributes;
    String command = attributes.Item(0).InnerText;
    // get parameters
    XmlNodeList list = document.GetElementsByTagName("arguments");
    List<string> listS = new List<string>();
    foreach (XmlNode l in list){
        listS.Add(l.InnerText);
    }
    Console.Write("YTplayer_FlashCall: \"" + command.ToString() + "(" + string.Join(",", listS) + ")\r\n");
    // Interpret command
    switch (command)
    {
        case "onYouTubePlayerReady": YTready(listS[0]); break;
        case "YTStateChange": YTStateChange(listS[0]); break;
        case "YTError": YTStateError(listS[0]);  break;
        default: Console.Write("YTplayer_FlashCall: (unknownCommand)\r\n"); break;
    }
}

this will resolve the flash->c# communication

calling the flash external functions (c#->flash):

private string YTplayer_CallFlash(string ytFunction){
    string flashXMLrequest = "";
    string response="";
    string flashFunction="";
    List<string> flashFunctionArgs = new List<string>();

    Regex func2xml = new Regex(@"([a-z][a-z0-9]*)(\(([^)]*)\))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    Match fmatch = func2xml.Match(ytFunction);

    if(fmatch.Captures.Count != 1){
        Console.Write("bad function request string");
        return "";
    }

    flashFunction=fmatch.Groups[1].Value.ToString();
    flashXMLrequest = "<invoke name=\"" + flashFunction + "\" returntype=\"xml\">";
    if (fmatch.Groups[3].Value.Length > 0)
    {
        flashFunctionArgs = pars*emphasized text*eDelimitedString(fmatch.Groups[3].Value);
        if (flashFunctionArgs.Count > 0)
        {
            flashXMLrequest += "<arguments><string>";
            flashXMLrequest += string.Join("</string><string>", flashFunctionArgs);
            flashXMLrequest += "</string></arguments>";
        }
    }
    flashXMLrequest += "</invoke>";

    try
    {
        Console.Write("YTplayer_CallFlash: \"" + flashXMLrequest + "\"\r\n");
        response = YTplayer.CallFunction(flashXMLrequest);                
        Console.Write("YTplayer_CallFlash_response: \"" + response + "\"\r\n");
    }
    catch
    {
        Console.Write("YTplayer_CallFlash: error \"" + flashXMLrequest + "\"\r\n");
    }

    return response;
}

private static List<string> parseDelimitedString (string arguments, char delim = ',')
{
    bool inQuotes = false;
    bool inNonQuotes = false;
    int whiteSpaceCount = 0;

    List<string> strings = new List<string>();

    StringBuilder sb = new StringBuilder();
    foreach (char c in arguments)
    {
        if (c == '\'' || c == '"')
        {
            if (!inQuotes)
                inQuotes = true;
            else
                inQuotes = false;

            whiteSpaceCount = 0;
        }else if (c == delim)
        {
            if (!inQuotes)
            {
                if (whiteSpaceCount > 0 && inQuotes)
                {
                    sb.Remove(sb.Length - whiteSpaceCount, whiteSpaceCount);
                    inNonQuotes = false;
                }
                strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());
                sb.Remove(0, sb.Length);                       
            }
            else
            {
                sb.Append(c);
            }
            whiteSpaceCount = 0;
        }
        else if (char.IsWhiteSpace(c))
        {                    
            if (inNonQuotes || inQuotes)
            {
                sb.Append(c);
                whiteSpaceCount++;
            }
        }
        else
        {
            if (!inQuotes) inNonQuotes = true;
            sb.Append(c);
            whiteSpaceCount = 0;
        }
    }
    strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());


    return strings;
}

adding Youtube event handlers:

private void YTready(string playerID)
{
    YTState = true;
    //start eventHandlers
    YTplayer_CallFlash("addEventListener(\"onStateChange\",\"YTStateChange\")");
    YTplayer_CallFlash("addEventListener(\"onError\",\"YTError\")");
}
private void YTStateChange(string YTplayState)
{
    switch (int.Parse(YTplayState))
    {
        case -1: playState = false; break; //not started yet
        case 1: playState = true; break; //playing
        case 2: playState = false; break; //paused
        //case 3: ; break; //buffering
        case 0: playState = false; if (!loopFile) mediaNext(); else YTplayer_CallFlash("seekTo(0)"); break; //ended
    }
}
private void YTStateError(string error)
{
    Console.Write("YTplayer_error: "+error+"\r\n");
}

usage ex:

YTplayer_CallFlash("playVideo()");
YTplayer_CallFlash("pauseVideo()");
YTplayer_CallFlash("loadVideoById(KuNQgln6TL0)");
string currentVideoId = YTplayer_CallFlash("getPlaylist()");
string currentDuration = YTplayer_CallFlash("getDuration()");

The functions YTplayer_CallFlash, YTplayer_FlashCall should work for any flash-C# communication with minor adjustments like the YTplayer_CallFlash's switch (command).

查看更多
叛逆
4楼-- · 2019-03-20 13:09

This stumped me for a number of hours.

Just add enable JS to your URL:

http://www.youtube.com/v/9bZkp7q19f0?version=3&enablejsapi=1

CallFunction works fine for me now! Also remove unrequired space in the call.

查看更多
该账号已被封号
5楼-- · 2019-03-20 13:10

I'd start by making sure that javascript can talk to your flash app.

make sure you have: allowScriptAccess="sameDomain" set in the embed (from http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/external/ExternalInterface.html#includeExamplesSummary).

you should validate that html->flash works; then C->html; and gradually work up to C->you-tube-component. you have a lot of potential points of failure between C and the you-tube-component right now and it's hard to address all of them at the same time.

查看更多
登录 后发表回答