Aaron Liu的部落格,紀錄我的生活點點滴滴

[電影]赤壁二-決戰天下 創傷大於創新的創意

| 2009年1月29日 星期四
本文有雷,慎入!


「羅貫中可以改寫三國志,吳宇森為什麼不可以說一套自己的三國故事?」

在看完第一集以後,一直給自己這樣的心理建設,但看完第二集還是忍不住要罵「合理一點吧!」

雖然說看到小喬入敵營、劉備殺入曹寨等等諸多大改的劇情十分意外,但這些都還可以算是「驚喜」,但劇中有太多細節讓人「訝異」!

例如
弓箭與連弩並列射擊:把長短程兵器擺在一起,怪得亂七八糟!
甘興用「魚油炸彈」炸毀木寨城牆:爆裂火器和燃燒火器差很多,爆裂式的火器到宋朝才出現吧!不愧是超時空的創意!
騎兵怕步兵:劇中曹營騎兵出來只敢繞著吳軍的龜甲方陣旁邊走,完全忽視基本的騎兵戰術「衝擊力」的應用,這種打法太可笑了吧!
打城腳的投石機:投石機和弓箭應該是不同距離互補的遠攻兵器,結果弓箭都開始射了投石機才打,那不成了打城腳的投石機!?不知道他彈道怎麼算的!?

總之,要創意球創新不是不行,但不求合理性的話,我只能說會帶給觀眾創傷啊!這部戲只能歸類於「歡樂武俠」系列,畫面熱鬧、大牌雲集、服裝講究、配樂精緻,一看就是砸大錢拍的,只可惜在細節上太不用心,以致於看得倒盡胃口啊!


繼續閱讀...

XDHome 0.6.2 HTPC播放軟體 小小改版

| 2009年1月18日 星期日
這次推出一個小小改版,解決檔案目錄不會更新的問題,這一版的程式,每次隱藏又重啟之後,都會去更新檔案清單,這樣就不會看到幽靈檔案了。

下載XDHome 0.6.2版



繼續閱讀...

[C#]全域鍵盤事件攔截(Low Level Keyboard Hook)

| 2009年1月11日 星期日
寫XDHome的時候其實只有兩個技術問題需要突破,一個是上次提過的影片播放,另一個就是要如何在視窗不被focus的時候也能夠抓到鍵盤。

基本上要解決這個問題,必須要呼叫Windows API去設定所謂的Low Level Hook,網路上有一些範例可以抓,基本上我參考了George MamaladzeHuan Lin兩人的著作,將程式改為指針對Keyboard的KeyDown事件的版本,程式碼如下:

Low Level Keyboard Hook服務程式
此段程式碼提供兩個static KeyDown Event供客戶端程式碼新增Even Handler

// Low Level Keyboard Hook
public class KeyboardHook
{

// System Code: Low Level Keyboard Hook
private const int WH_KEYBOARD_LL = 13;

// System Event Code: Key Down Event
private const int WM_KEYDOWN = 0x100;

// System Event Code: System Key Down Event
private const int WM_SYSKEYDOWN = 0x104;

// Stores the handle to the Keyboard hook procedure.
private static int s_KeyboardHookHandle;

//是否只由這個Global Hook抓取鍵盤事件
public static bool globalControlOnly = false;

//Private KeyDown Event, 與GlobalKeyDown配合使用
private static event KeyEventHandler _globalKeyDown;

// Public KeyDown Event
// 每次只能一個Event Handler處理這個Event
// 加入和解除EventHandler時會自動安裝和解除Hook
public static event KeyEventHandler GlobalKeyDown
{
add
{
KeyboardHook.HookKeyboard();
KeyboardHook._globalKeyDown += value;
}
remove
{
KeyboardHook._globalKeyDown -= value;
KeyboardHook.UnhookKeyboard();
}
}




// 當hook抓到key event時的處理程序
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

// Hook handle
private static int m_HookHandle = 0;

// Keyboard Hook函式指標
private static HookProc m_KbdHookProc;

// WinAPI 取得Module Handle
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

// WinAPI 加入Hook
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);


// WinAPI 解除Hook
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);


// WinAPI 將Event傳給下一個Hook,如不執行此項,則只有這個Hook會被執行
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);


private static void HookKeyboard()
{
if (m_HookHandle == 0)
{

KeyboardHook.s_KeyboardHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);

using (Process curProcess = Process.GetCurrentProcess())
{
using (ProcessModule curModule = curProcess.MainModule)
{
m_KbdHookProc = new HookProc(KeyboardHookProc);
m_HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, m_KbdHookProc, GetModuleHandle(curModule.ModuleName), 0);
}
}

if (m_HookHandle == 0)
{
throw new Exception("Install Global Keyboard Hook Faild.");
}

}
}


private static void UnhookKeyboard()
{

if (m_HookHandle != 0)
{
bool ret = UnhookWindowsHookEx(m_HookHandle);
if (ret)
{
m_HookHandle = 0;
}
else
{
throw new Exception("Uninstall Global Keyboard Hook Faild.");
}

}

}





private static int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
bool handled = false;

if(nCode >= 0)
{

if ((int)wParam == WM_KEYDOWN || (int)wParam == WM_SYSKEYDOWN)
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);

if (KeyboardHook.globalControlOnly)
{
e.Handled = true;
}
else
{
e.Handled = false;
}

_globalKeyDown.Invoke(null, e);

handled = e.Handled;
}

}


if (KeyboardHook.globalControlOnly) return -1;

return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);


}



[StructLayout(LayoutKind.Sequential)]
private struct KeyboardHookStruct
{
///
/// Specifies a virtual-key code. The code must be a value in the range 1 to 254.
///

public int VirtualKeyCode;
///
/// Specifies a hardware scan code for the key.
///

public int ScanCode;
///
/// Specifies the extended-key flag, event-injected flag, context code, and transition-state flag.
///

public int Flags;
///
/// Specifies the Time stamp for this message.
///

public int Time;
///
/// Specifies extra information associated with the message.
///

public int ExtraInfo;
}

}


客戶端程式碼,Handel static的全域KeyDown Event

private void KeyboardHook_KeyDown(object sender, KeyEventArgs e)
{
string key = e.KeyCode.ToString(); //取得KeyCode字串
//TODO: 加入處理KeyDown事件的程式
}


要加入全域事件時

KeyboardHook.globalControlOnly = true; //只有Global KeyDown會起作用,其他程式不能攔截鍵盤事件
KeyboardHook.GlobalKeyDown += KeyboardHook_KeyDown;


要取消全域事件處理時

KeyboardHook.GlobalKeyDown -= KeyboardHook_KeyDown;


繼續閱讀...

XDHome 0.6.1 HTPC播放軟體 小改版釋出

|
下載XDHome 0.6.1

XDHome 0.6.1版釋出,這次除了修正一些bug之外,還將原始檔一併公布。

修正: Pause鍵按第二次時不會恢復播放狀態

目前bin和原始檔都公布在SourceForge上面,原始碼以LGPL方式授權,歡迎有興趣的朋友一起研究。

繼續閱讀...

[分享]XDHome的由來 - 窮人的HTPC改造計畫

| 2009年1月7日 星期三
用客廳裡的大電視看低調影片一直是我的夢想,特別是有一台HTPC能身兼動物機和播放機的話,就更棒了!無奈阮囊羞澀,要買一台小PC加上iMon之類的周邊的話,耗費不少小朋友的!
前一陣子藉著搬家的機會,下定決心敗入Benq HS3742,但沒有HTPC的他,只是讓Full HD的性能空顯寂寞而已!


主角1 Benq SH3742


Full HD貼紙驗明正身

剛好,家裡還有學生時代留下來的HP NC6000筆電,雖然是五年前的古董,但當一台動物機兼播放機應該還綽綽有餘吧!?以VGA線插上電視一試竟然可以上1920 x 1080的Full HD解析度!雖然只能在單螢幕模式做這種輸出,但對於這台老古董來講,真是喜出望外啊!不讓他當HTPC真是太可惜了!


主角2 HP NC6000

1920x1080 Full HD輸出


有了這個想法以後,尋找電腦遙控器就成了關鍵。像iMon這樣動輒兩三張小朋友的高貴產品,顯然不合乎我克難的需求,用微軟的MCE遙控器是當時主要的考量之一,可是我偶然間逛到Y拍上有賣這款大樂RM03遙控器,「號稱」支援我最熟悉的Media Player Classic,似乎有機會擺脫Windows MCE,而且售價又在一張小朋友以內,於是馬上敗入。


大樂RM-03 電腦遙控器

所謂代誌不是憨人想的那樣簡單,RM-03雖然號稱支援Media Player Classic,可是Media Player Classic沒有瀏覽檔案的功能,字體也太小,而且RM-03附的驅動程式我個人也不是很能接受,於是使用RM03方案很快就被我判出局了!

所謂山不轉路轉,遙控器附的程式我不滿意,大不了我自己寫一個啊!於是XDHome這套軟體誕生了。由於RM03在不裝驅動程式的狀態下就是一支鍵盤,因此XDHome就把基本的播放功能,如開檔、暫停、快轉、倒轉、全螢幕....等指令變成以鍵盤的方式觸發,再加上一個可以自訂按鈕的介面,RM03就變得頗好用了!

有圖有真相:


用XDHome選擇要播放的檔案


全螢幕播放720p的影片


電視+NB+遙控器接收器大合照


嘿嘿~~這就是窮人HTPC大改造!不到一張小朋友就解決哩!

繼續閱讀...

XDHome 0.6 電視專用播放器

| 2009年1月5日 星期一
下載XDHome 0.6版

這個版本較前一版(0.1)有許多的改善,我還是重新介紹一下好了。

XDHome是一個可以完全用遙控器/鍵盤操作的影片播放器,專門用來將電腦內的影片在電視上播放用的,所以它可以:
  1. 以大字體瀏覽檔案,讓人在電視上也看得清楚檔名。
  2. 自行設定所有功能與鍵盤的對應,讓任何遙控器都可以配合使用。
  3. 播放影片檔,只要有裝正確的CODEC就可以播出,同時也支援外掛字幕。



0.6版較0.1版更新的功能有:
  1. 有完整的檔案系統瀏覽功能
  2. 新增自訂按鍵功能
  3. 新增捷徑功能
  4. 影片播放改用DirectShowNet,沒有相容問題
  5. 全新的Keyboard Hook,較前版穩定

使用的時候請注意:
  1. XDHome開啟時會攔截所有的鍵盤事件,其他程式會抓不到鍵盤,但少數系統鍵還是可以運作(例如Ctrl+Alt+Del)
  2. XDHome是以C#基於.Net 2.0和DirectShowNet 2.0開發的,必須要安裝.Net Framework 2.0和DirectX 9以上版本才能執行。
  3. 目前只能播放影片檔(avi、wmv、rm、rmvb、mkv),但是DVD還不行。
臭蟲還沒完全抓完,但應該還勘用,有用到什麼問題的話,請寫在回應裡面。

繼續閱讀...

[C#]使用DirectShowNet播放影片檔

| 2009年1月1日 星期四
DirectShowNetSourceForge上面的開放源碼函示庫,基本上它將DirectShow 9包裝成.Net 2.0的函示庫,使用上其實有點複雜,要用的話,請先下載DirectShowNet 2.0。程式碼是按照DirectShowNet的範例改出來的,案例可以從這裡下載。

我將一些播放影片檔的基本功能功能寫成含註解的程式碼如下:


/// <summary>
/// 播放狀態
/// </summary>
public enum PlayState
{
Stopped, //視訊停止
Paused, //視訊暫停
Playing, //視訊播放中
Init //尚未開啟任何影片檔
};

/// <summary>
/// 播放Video物件
/// </summary>
public class Video
{

private const int WMGraphNotify = 0x0400 + 13;
public const int VolumeFull = 0;
public const int VolumeSilence = -10000;

private DirectShowLib.IGraphBuilder graphBuilder = null;
private DirectShowLib.IMediaControl mediaControl = null;
private DirectShowLib.IMediaEventEx mediaEventEx = null;
private DirectShowLib.IVideoWindow videoWindow = null;
private DirectShowLib.IBasicAudio basicAudio = null;
private DirectShowLib.IBasicVideo basicVideo = null;
private DirectShowLib.IMediaSeeking mediaSeeking = null;
private DirectShowLib.IMediaPosition mediaPosition = null;
private DirectShowLib.IVideoFrameStep frameStep = null;

private string filePath = string.Empty;
private bool _FullScreen = false;
private int _Volume = VolumeFull;
public PlayState State = PlayState.Init;
private double currentPlaybackRate = 1.0;

private Size _size = new Size(0, 0);
private IntPtr hDrain = IntPtr.Zero;

#if DEBUG
private DsROTEntry rot = null;
#endif

/// <summary>
/// 裝載視訊物件的Container
/// </summary>
public Control Owner;


/// <summary>
/// 是否全螢幕模式
/// </summary>
public bool FullScreen
{
get
{
return _FullScreen;
}
set
{
if (this.State == PlayState.Init)
{
throw new Exception("No video file has been opened");
}

if (value != _FullScreen)
{
int hr = 0;
OABool lMode;

if (value)
{
//設定為全螢幕
hr = this.videoWindow.get_MessageDrain(out hDrain);
DsError.ThrowExceptionForHR(hr);
hr = this.videoWindow.put_MessageDrain(Owner.Handle);
DsError.ThrowExceptionForHR(hr);

lMode = OABool.True;
hr = this.videoWindow.put_FullScreenMode(lMode);
DsError.ThrowExceptionForHR(hr);
}
else
{
//回復視窗模式
lMode = OABool.False;
hr = this.videoWindow.put_FullScreenMode(lMode);
DsError.ThrowExceptionForHR(hr);

hr = this.videoWindow.put_MessageDrain(hDrain);
DsError.ThrowExceptionForHR(hr);
hr = this.videoWindow.SetWindowForeground(OABool.True);
DsError.ThrowExceptionForHR(hr);
}
}

this._FullScreen = value;

}

}

/// <summary>
/// 取得/設定寬度
/// </summary>
public int Width
{
get
{
return _size.Width;
}
set
{
if (this.State == PlayState.Init)
{
throw new Exception("No video file has been opened");
}

int hr = 0;
this.Owner.Width = value;
hr = this.videoWindow.SetWindowPosition(0, 0, value, _size.Height);
DsError.ThrowExceptionForHR(hr);
this._size.Height = value;
}
}

/// <summary>
/// 取得/設定高度
/// </summary>
public int Height
{
get
{
return _size.Height;
}
set
{
if (this.State == PlayState.Init)
{
throw new Exception("No video file has been opened");
}

int hr = 0;
this.Owner.Height = value;
hr = this.videoWindow.SetWindowPosition(0, 0, _size.Width, value);
DsError.ThrowExceptionForHR(hr);
this._size.Height = value;
}
}

/// <summary>
/// 取得目前影片的長度(秒數)
/// </summary>
public long Duration
{
get
{
if (State == PlayState.Init)
{
return 0;
}
else
{
int hr = 0;
long dur;
hr = this.mediaSeeking.GetDuration(out dur);
DsError.ThrowExceptionForHR(hr);

TimeSpan t = new TimeSpan(dur);
return (long)t.TotalSeconds;
}
}
}

/// <summary>
/// 目前的位置(秒數)
/// </summary>
public long Position
{
get
{
if (State == PlayState.Init)
{
return 0;
}
else
{
int hr = 0;
long pos, stop;
hr = this.mediaSeeking.GetPositions(out pos, out stop);

TimeSpan t = new TimeSpan(pos);


return (long)t.TotalSeconds;
}
}

set
{
if (this.State == PlayState.Init)
{
throw new Exception("No video file has been opened");
}

if (Position < 0 || Position > Duration)
{
throw new Exception("Position out of range");
}

int hr = 0;
TimeSpan t = new TimeSpan(0, 0,(int)value);

DsLong newPosition = new DsLong(t.Ticks);
hr = this.mediaSeeking.SetPositions(newPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);
}
}


/// <summary>
/// 目前的音量大小 0~100
/// </summary>
public int Volume
{
get
{

return this._Volume / 100 + 100;
}
set
{
if (value > 100 || value < 0)
{
throw new Exception("Volume must between 0 to 100");
}

int hr = 0;
this._Volume = (value - 100) * 100;
hr = this.basicAudio.put_Volume(this._Volume);
}
}




/// <summary>
/// 初始化Video物件
/// </summary>
/// <param name="owner">Video附著的Container 通常是一個Pannel或Form</param>
public Video(ref Control owner)
{
this.Owner = owner;
}

/// <summary>
/// 開啟視訊檔
/// </summary>
/// <param name="filePath">視訊檔案路徑</param>
/// <param name="autoPlay">開啟後是否自動播放</param>
public void Open(string filePath, bool autoPlay)
{
int hr = 0;

if (!System.IO.File.Exists(filePath))
{
throw new Exception("File " + filePath + "Not Found!");
}


if (State != PlayState.Init)
{
CloseVideo();
}


//取得檔案stream
this.graphBuilder = (DirectShowLib.IGraphBuilder)new FilterGraph();
hr = this.graphBuilder.RenderFile(filePath, null);
DsError.ThrowExceptionForHR(hr);


//Get DirectShow Control interfaces
this.mediaControl = (DirectShowLib.IMediaControl)this.graphBuilder;
this.mediaEventEx = (DirectShowLib.IMediaEventEx)this.graphBuilder;
this.mediaSeeking = (DirectShowLib.IMediaSeeking)this.graphBuilder;
this.mediaPosition = (DirectShowLib.IMediaPosition)this.graphBuilder;

this.videoWindow = this.graphBuilder as DirectShowLib.IVideoWindow;
this.basicVideo = this.graphBuilder as DirectShowLib.IBasicVideo;
this.basicAudio = this.graphBuilder as DirectShowLib.IBasicAudio;

if (!IsVideo())
{
throw new Exception("The File Is Not A Video File Or The Codec Is Unsupported!");
}

//Set the playback event handle object
//hr = this.mediaEventEx.SetNotifyWindow(this.Owner.Handle, WMGraphNotify, IntPtr.Zero);
//DsError.ThrowExceptionForHR(hr);

//Set the video owner object to contain the vidwo window
hr = this.videoWindow.put_Owner(this.Owner.Handle);
DsError.ThrowExceptionForHR(hr);

//Set video window layout
hr = this.videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipSiblings | WindowStyle.ClipChildren);
DsError.ThrowExceptionForHR(hr);


//Get vedio size
int lHeight, lWidth;
hr = this.basicVideo.GetVideoSize(out lWidth, out lHeight);
DsError.ThrowExceptionForHR(hr);

//set owner object size as video size
this.Owner.Width = lWidth;
this.Owner.Height = lHeight;
this._size.Width = lWidth;
this._size.Height = lHeight;

hr = this.videoWindow.SetWindowPosition(0, 0, lWidth, lHeight);
DsError.ThrowExceptionForHR(hr);


GetFrameStepInterface();


this._FullScreen = false;
this.currentPlaybackRate = 1.0;

this.State = PlayState.Stopped;
this.filePath = filePath;

if (autoPlay)
{
Play();
}


}


/// <summary>
/// 播放視訊檔
/// </summary>
public void Play()
{
if (State == PlayState.Stopped || State == PlayState.Paused)
{
int hr = 0;
hr = this.mediaControl.Run();
DsError.ThrowExceptionForHR(hr);

this.State = PlayState.Playing;
}


}

/// <summary>
/// 停止播放視訊檔
/// </summary>
public void Stop()
{
if (State == PlayState.Playing || State == PlayState.Paused)
{
int hr = 0;

//停止播放
hr = this.mediaControl.Stop();
DsError.ThrowExceptionForHR(hr);

//回到第一個影格
DsLong pos = new DsLong(0);
hr = this.mediaSeeking.SetPositions(pos, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);
//顯示第一個影格
hr = this.mediaControl.Pause();

this.State = PlayState.Stopped;
}
}


/// <summary>
/// 停止播放視訊檔
/// </summary>
public void Pause()
{
if (State == PlayState.Playing)
{
int hr = 0;
hr = this.mediaControl.Pause();
DsError.ThrowExceptionForHR(hr);

this.State = PlayState.Paused;
}
}

/// <summary>
/// 前進
/// </summary>
/// <param name="second">前進的秒數</param>
public void Forward(int second)
{
if (Position + second < Duration)
{
Position += second;
}
else
{
Position = Duration;
}

}

/// <summary>
/// 倒退
/// </summary>
/// <param name="second">倒退的秒數</param>
public void Backward(int second)
{
if (Position - second > 0)
{
Position -= second;
}
else
{
Position = 0;
}

}


/// <summary>
/// 視訊檔案是否是一個被支援的視訊檔?
/// </summary>
/// <returns>true = 是; false = 否</returns>
private bool IsVideo()
{
int hr = 0;
OABool lVisible;
bool result = true;

if ((this.videoWindow == null) || (this.basicVideo == null))
{
//是一個Audio檔(沒有視訊,只有音訊)
result = false;
}


hr = this.videoWindow.get_Visible(out lVisible);
if (hr < 0)
{
// 視訊檔使用的Codec不被支援的或根本不是視訊檔
if (hr == unchecked((int)0x80004002)) //E_NOINTERFACE
{
result = false;
}
else
DsError.ThrowExceptionForHR(hr);
}

return result;
}


/// <summary>
/// 取得格放時要用的IVideoFrameStep介面
/// </summary>
/// <returns>true = 可以取得格放介面 false = 無法取得格放介面</returns>
private bool GetFrameStepInterface()
{
int hr = 0;

DirectShowLib.IVideoFrameStep frameStepTest = null;

// Get the frame step interface, if supported
frameStepTest = (DirectShowLib.IVideoFrameStep)this.graphBuilder;

// Check if this decoder can step
hr = frameStepTest.CanStep(0, null);
if (hr == 0)
{
this.frameStep = frameStepTest;
return true;
}
else
{
this.frameStep = null;
return false;
}
}



/// <summary>
/// 關閉視訊檔
/// </summary>
public void CloseVideo()
{
int hr = 0;

//停止播放
if (this.mediaControl != null)
hr = this.mediaControl.Stop();

// Clear global flags
this.State = PlayState.Stopped;
this.FullScreen = false;

//關閉DirectShow Control Interfece
CloseInterfaces();


this.filePath = string.Empty;
this.State = PlayState.Init;
}

/// <summary>
/// 關閉DirectShow的Control Interface
/// </summary>
private void CloseInterfaces()
{
int hr = 0;

try
{
lock (this)
{
// Relinquish ownership (IMPORTANT!) after hiding video window

if (this.videoWindow != null)
{
hr = this.videoWindow.put_Visible(OABool.False);
DsError.ThrowExceptionForHR(hr);
hr = this.videoWindow.put_Owner(IntPtr.Zero);
DsError.ThrowExceptionForHR(hr);
}
if (this.mediaEventEx != null)
{
hr = this.mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
DsError.ThrowExceptionForHR(hr);
}

#if DEBUG
if (rot != null)
{
rot.Dispose();
rot = null;
}
#endif
// Release and zero DirectShow interfaces
if (this.mediaEventEx != null)
this.mediaEventEx = null;
if (this.mediaSeeking != null)
this.mediaSeeking = null;
if (this.mediaPosition != null)
this.mediaPosition = null;
if (this.mediaControl != null)
this.mediaControl = null;
if (this.basicAudio != null)
this.basicAudio = null;
if (this.basicVideo != null)
this.basicVideo = null;
if (this.frameStep != null)
this.frameStep = null;
if (this.graphBuilder != null)
Marshal.ReleaseComObject(this.graphBuilder); this.graphBuilder = null;

GC.Collect();
}
}
catch(Exception ex)
{
throw new Exception("Video close faild:\r\n" + ex.Message);
}
}






}

根據我自己的實測,這樣寫出來的播放器,只要Codec有裝幾乎什麼檔都可以播,包含AVI、WMV、MKV都已經測過OK,連str外掛字幕檔都可以顯示,還不賴。

繼續閱讀...