blog.Ring.idv.tw

Articles

HTML5 video autoplay on iOS

許多時候我們在使用iPhone/iPad瀏覽一些包含HTML5 video的網頁時,會發現為何網頁中的video都不會自動播放?而使用一般PC平臺上的瀏覽器卻又都能自動播放?

根據iOS-Specific Considerations的說明如下:

In Safari on iOS (for all devices, including iPad), where the user may be on a cellular network and be charged per data unit, 
preload and autoplay are disabled. No data is loaded until the user initiates it. This means the JavaScript play() and load() methods 
are also inactive until the user initiates playback, unless the play() or load() method is triggered by user action. 
In other words, a user-initiated Play button works, but an onLoad="play()" event does not.

簡單來說~ 其實主要是用來防止「以量計價」的行動網路費爆增~ 這考量的確沒錯! 但是為何不將此功能改為可選項開關呢?因為現階段有許多採用資費吃到飽方案或是用WiFi網路的客群,若能提供可選項開關或是像使用GPS定位一樣的作法,主動彈出一個confirm視窗來提示是否允許自動播放也不失為一種方式呀!

所以~ 現階段如果想在iPhone/iPad瀏覽器中播放video那就得透過使用者觸發才行... Orz 話雖如此,但其實還可以透過另一種方式來達到video自動播放,那就是透過UIWebView元件來達成,也就是說~ 如果你是採用PhoneGap開發的應用程式,那恭禧你~ 只需要對AppDelegate.m 原始碼加上下述設定:

theWebView.allowsInlineMediaPlayback = YES;
theWebView.mediaPlaybackRequiresUserAction = NO;

同時也需要針對你的video element加上「webkit-playsinline」attribute即可,如:

<video id="video" src="xxx.mp4" autoplay webkit-playsinline></video>

現在開啟你的App就能看到video自動播放!

不過iOS Safari針對Canvas的支援仍有些不足~ 所以上述HTML5 Canvas&Video: Big Buck Bunny範例就算移植到UIWebView仍然無法看到效果的~ 因為iOS Safari針對Canvas少了下述APIs的支援:

void drawImage(HTMLVideoElement image, double dx, double dy);
void drawImage(HTMLVideoElement image, double dx, double dy, double dw, double dh);
void drawImage(HTMLVideoElement image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh);

如此就只能被迫將video轉成一張張image才能達到類似的效果了...

參考資源

UNSOLVED HTML5 VIDEO ISSUES ON IOS

iPad and iPhone html5 video autoplay

2012-01-04 10:41:59 | Add Comment

淺談Canvas - save/restore

CanvasHTML5規格中的一部份,它允許你可以用程式來繪製一些2D圖形或是點陣圖,通常也被拿來製作HTML5遊戲之用,而本文主要介紹Canvas API中最常被初學者混淆的一組函式,它們分別為:「save()」和「restore()」,簡單來說~ 「save()」主要是用來保存目前Canvas的狀態,例如:style, lineWidth, font等,透過「save()」函式的呼叫它會將目前Canvas的狀態「Push」到「Stack」之中,而「restore()」函式就是從「Stack」來「Pop」出上一個Canvas的狀態,讀到這邊如果您已經了解「save/restore」的話,那非常恭禧你已經掌握要領了,反之~ 如果仍然有所疑惑的話,請看下面的範例介紹:

實作範例

本文將透過兩種不同的方式來實作出上圖的範例,以方便理解為何需要「save/restore」函式,使用它有何好處?

範例1 - 不採用save/restore

範例1完整的程式碼如下:(筆者假設您已經有基本的Canvas概念,所以會跳過一些基本的解說)

<!DOCTYPE html>
<html><head><meta charset="utf-8"></head><body>
<canvas id="a_canvas" width="200" height="150"></canvas>
<script type="text/javascript">
  var canvas = document.getElementById("a_canvas");
  var ctx = canvas.getContext("2d");
  function drawRect(context, color) {
    context.fillStyle = color;
    context.fillRect(0, 0, 100, 30);
  }
  function rotateDeg(context, deg) {
    var rad = deg * Math.PI / 180;
    context.rotate(rad);
  }
  drawRect(ctx, "red");
  ctx.translate(100,30); 
  rotateDeg(ctx, 45);    
  drawRect(ctx, "blue");
  rotateDeg(ctx, 45);    
  ctx.translate(20, -100); 
  drawRect(ctx, "green");
</script>
</body></html>

P.S. 下述圖形的虛線都作為解說之用,並非繪製在Canvas上的線條

drawRect(ctx, "red");

透過自訂的「drawRect()」函式,然後在Canvas上繪製一個從座標點(0,0)的位置畫一個長為100px寬為30px的紅色矩形。

ctx.translate(100,30); 

將目前Canvas的基準點位置設為(100,30),如上圖所示。

P.S. Canvas基準點位置預設為左上角,和Flash座標系統一致。

rotateDeg(ctx, 45);
drawRect(ctx, "blue");

透過自訂的「rotateDeg()」函式將Canvas往順時針旋轉45度,並繪製一個藍色矩形。

rotateDeg(ctx, 45);

接著再進行一次「rotateDeg()」函式將Canvas再往順時針旋轉45度。(此時已旋轉90度)

ctx.translate(20, -100);

到這邊的程式碼就是重點了,理論上如果以基準點為(0,0)的位置來說,目前要設定的相對基準點位置就是位於(200,50)的位置,但是在沒有運用「save/restore」的方式之下,我們必須自己在腦海裡進行一下矩陣轉換,同時根據上一次的基準點位置(100,30)以重設目前基準點位置(20,-100),最後再透過「drawRect(ctx, "green");」繪製出綠色的矩形出來。

範例2 - 採用save/restore

範例2完整的程式碼如下:

<!DOCTYPE html>
<html><head><meta charset="utf-8"></head><body>
<canvas id="a_canvas" width="200" height="150"></canvas>
<script type="text/javascript">
  var canvas = document.getElementById("a_canvas");
  var ctx = canvas.getContext("2d");
  function drawRect(context, color) {
    context.fillStyle = color;
    context.fillRect(0, 0, 100, 30);
  }
  function rotateDeg(context, deg) {
    var rad = deg * Math.PI / 180;
    context.rotate(rad);
  }
  drawRect(ctx, "red");
  ctx.save();            
  ctx.translate(100,30); 
  rotateDeg(ctx, 45);    
  drawRect(ctx, "blue");
  ctx.restore();         
  ctx.translate(200,50); 
  rotateDeg(ctx, 90);    
  drawRect(ctx, "green");
</script>
</body></html>

drawRect(ctx, "red");
ctx.save();            

除了在Canvas上繪製一個紅色的矩形外,這邊還呼叫了「save()」函式,意指為將目前的基準點位置(0,0)狀態「Push」到「Stack」中,以方便待會取回。

ctx.translate(100,30); 
rotateDeg(ctx, 45);    
drawRect(ctx, "blue");

將目前Canvas的基準點位置設為(100,30),同時旋轉45度並繪製上一個藍色矩形。

ctx.restore(); 

這裡呼叫「restore()」函式的重點就是要將剛剛「Push」到「Stack」的Canvas狀態取回,也就是重設基準點位置回到(0,0),並忘掉「save()」和「restore()」之間所設定的Canvas狀態,如:translate()、rotate()。

ctx.translate(200,50); 
rotateDeg(ctx, 90);    
drawRect(ctx, "green");

最後再重設基準點位置為(200,50),並順時針旋轉90度,然後畫上綠色矩形即大功告成,到這邊可以發現端倪了嗎?假設我們不採用「save/restore」函式來輔助的話,我們需要去進行矩陣轉換,而這只是個小範例~ 如果更複雜的圖形那勢必會轉到頭昏眼花... Orz,而透過「save/restore」函式的輔助,不僅讓程式碼的可讀性大大地增加,而且維護上也較為容易些嚕。

參考資源

Understanding save() and restore() for the Canvas Context

2011-11-02 22:14:01 | Add Comment

一柱香

地點:鹿港‧天后宮

機身:Sony A550

鏡頭:Carl Zeiss 135mm T* F1.8

快門:1/100s

光圈:F2.8

ISO:200

曝光補償:-0.7步

這張照片其實是今年的端午節在鹿港所拍攝的,直覺看起來要拍這張照片似乎沒有太多的困難~ 然而事實卻並非如此~

首先~ 這個角度當時在我的心中早就已經構圖好了,所以當我站在那個角度之後~ 接下來就是等一位位民眾在進行插香的動作時,而我同時瞬間按下快門即可~ 但問題就來了~ 因為整張圖的左半邊暗色系偏多~ 所以我希望插香民眾「手」的肢體是呈現較白皙的皮膚色~ 當然這完全無法控制.. 同時要注意到整張圖我不希望有「多餘」的事物出現在畫面當中~ 光這兩點我就必須站在原地持續拍攝到滿意為止~

而上圖就是我最滿意的一張,不僅滿足上述所提到的條件~ 同時手的肢體胖瘦適中而且還戴著「佛珠」,所以直到最後拍到這張我才滿意的離去。

2011-10-25 00:14:21 | Comments (2)

鸚鵡螺 - 黃金分割比例

圖片來源:http://www.dailymail.co.uk/news/index.html

剛剛看到一篇網路蘋果新聞,該新聞的標題為:「5歲小女孩 隨手挖出1.6億年前化石」,重點就在於該化石為何物?點進去看會發現該名小女孩挖到了1.6億年前的「鸚鵡螺」化石,這對一般人來說或許沒什麼值得注意的,但如果你有在玩攝影或是設計相關背景的話,我想當你看到「鸚鵡螺」時應該也會馬上聯想到「黃金分割比例」(golden section proportions),那何謂黃金分割比例?有無實際例子?當然有,而且生活上隨處可見~ 不過在介紹實例之前,先來了解一下何謂黃金分割比例?根據「設計幾何學」一書的解釋,在19世紀的一位德國心理學家「費希納」(Gustav Fechner),還有1908年的拉羅(Édouard Lalo)都曾對於不同比例的「矩形」偏好進行了實驗,測試人們對於不同比例的矩形偏好,結果在「最喜歡的矩形」項目中由「5:8」比例的矩形勝出,也就是「1:1.618」俗稱所謂的「黃金分割比例」。

對Apple的iCloud icon有印象嗎?它就是採用黃金分割比例所設計出來的~

圖片來源:iCloud Logo Infused With Golden Ratio

而且不只是設計,就在數學程式裡的「費氏數列」(Fibonacci Number)中兩兩相鄰的數字也存在著黃金分割比例的比值,例如:「0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 …」,此數列裡兩相鄰數字的比值會逐漸接近黃金分割比例,也就是「1:1.618」。

若再從攝影構圖的角度來看,其實只要掌握簡單的黃金分割構圖,基本上都能獲得一張還不錯看的照片,不過這當然是在有準焦的前提之下!

圖片來源:Golden Section and Photography

最後為何看到「鸚鵡螺」化石會聯想到「黃金分割比例」呢?因為「鸚鵡螺」體腔內的生長紋路也是相當接近「黃金分割比例」,當然還有許多關於黃金分割比例的實例,如電影、建築、音樂等...。

圖片來源:浅谈apple设计中的黄金分割

所以身為工程師的你,仍然覺得設計師就是畫畫圖而已嗎?其實可以多請教他們為何採用這樣的設計方式?為何箭頭的方向在上方?為何採用溫暖的色系?.....

2011-09-14 00:56:51 | Comments (1)

PhoneGap 1.0.0 for Android

PhoneGap 最初是由「Nitobi」這間位於加拿大的公司在2008年8月所發起的,而到2010年中IBM也開始參與投入研發。它是一套能讓你選擇採用「New BSD」或「MIT」license的Open Source Mobile Framework,它主要的目的在於能讓開發人員透過一些Web技術(HTML+JavaScript)來存取行動裝置的Native API,而且它支援了Android、iOS、Blackberry、WebOS、Windows Phone 7等平台的支援,就在上個月的29日它發佈了PhoneGap 1.0.0。

開發人員只需要撰寫一份HTML+JavaScript的程式,就可以在這些平台上存取這些行動裝置所提供的功能,如:Camera, Compass, Contacts等...,然而為何需要有這個Framework的存在呢?這最大的原因就在於我們現階段仍無法透過Web來存取上述這些裝置功能,而W3C雖然正在針對行動裝置制定標準的Device APIs,不過現階段仍無法透過Web來存取照相機或相簿等功能(Android 3.0將支援Media Capture API),所以你可以將PhoneGap當做是一個「過渡性」的產品,或者等未來Web都已支援這些Native APIs,但卻有一致性或相容性等其它問題時,也許那時PhoneGap仍然是一個好的選擇,這就像是HTML5與Flash現階段的情況,看看Google+首波合作的遊戲商就可以知道了.. 16款遊戲中有15款採用Flash來開發

探討 PhoneGap 1.0.0 for Android

本文並不是一篇教你如何使用PhoneGap建立HelloWorld的無痛上手,而是將深入探討PhoneGap 1.0.0在Android平台上如何扮演Web與Native API之間的橋梁角色,有興趣的朋友請至Github - phonegap下載。

本文會採用Notification.alert這個簡單的API來說明這之間的流程,至於為什麼JavaScript上早已經有alert功能,為何還需要Notification.alert呢?這兩者之間最大的差別就在於Notification.alert可以讓你「客製化」一些資訊,如:標題(title)或按鈕上的文字(buttonLabel)。

下述是PhoneGap所定義的Notification.alert的API

navigator.notification.alert(message, alertCallback, 〔title〕, 〔buttonName〕)

用法如下:

function alertDismissed() {
    // do something
}
navigator.notification.alert(
    'You are the winner!',  // message
    alertDismissed,         // callback
    'Game Over',            // title
    'Done'                  // buttonName
);

JS - Notification.prototype.alert

接著直接將PhoneGap的JS原始碼翻來看(phonegap-1.0.0.js),它會執行「PhoneGap.exec」。

Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) {
    var _title = (title || "Alert");
    var _buttonLabel = (buttonLabel || "OK");
    PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]);
};

JS - PhoneGap.exec

這部份就是JavaScript如何和Native API溝通的橋梁,這裡有個「callbackId」,它是由「service name + counter」所組成的,例:Notification2,並以此callbackId為key,以callback funcation為value先暫存在PhoneGap.callbacks的陣列。

PhoneGap.exec = function(success, fail, service, action, args) {
    try {
        var callbackId = service + PhoneGap.callbackId++;
        if (success || fail) {
            PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
        }
        
        var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));

而重點就在於下面這一行:

var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));

prompt」?沒錯~ PhoneGap 1.0.0 for Android就是透過JavaScript的「prompt」function將相關參數傳到原生的Java程式中,為何能這樣達成?原因就在於Android所設計的WebView元件,它讓開發人員能夠透過WebChromeClient來自訂JavaScript中的alert, confirm, prompt等功能的處理方式,而上述函式實際傳送到Java中的參數其實就如下圖所示:

DroidGap.java - onJsPrompt

上述所提供的「prompt」實際上就是呼叫DroidGap.java中的「onJsPrompt」函式(line:868),更確切的說「onJsPrompt」是被宣告在GapClient(inner class)中,詳細的細節請參考Source code。

public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

而「onJsPrompt」處理的方式就是先判斷所傳進來的「defaultValue」是否為「gap:」字串開頭,以本文的為例就是將「gap:['Notification','alert','Notification2',true]」進行拆解,再透過「pluginManager.exec」去執行。

if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
       	JSONArray array;
        try {
        	array = new JSONArray(defaultValue.substring(4));
        	String service = array.getString(0);
        	String action = array.getString(1);
        	String callbackId = array.getString(2);
        	boolean async = array.getBoolean(3);
        	String r = pluginManager.exec(service, action, callbackId, message, async);
        	result.confirm(r);
        } catch (JSONException e) {
        	e.printStackTrace();
        }
}

PluginManager.java

基本上在PhoneGap for Android的原始碼中,它所提供的各式API也都稱為Plugin,意指為:Notification、Compass、Camera..等都稱為Plugin,實際上這些一個個的功能也都繼承於Plugin class(它實作IPlugin介面),所以該PluginManager就是用來管理這些Plugin。

在「pluginManager.exec」的函式中,主要就是取得這些特定的Plugin物件,並透過「plugin.execute」來執行。

PluginResult cr = plugin.execute(action, args, callbackId);  //line:119 in PluginManager.java

Notification.java

由於本文以Notification為例,所以上述函式(plugin.execute)會執行「Notification.execute」。

public PluginResult execute(String action, JSONArray args, String callbackId) {
		PluginResult.Status status = PluginResult.Status.OK;
		String result = "";		
		
		try {
			if (action.equals("beep")) {
				this.beep(args.getLong(0));
			}
			else if (action.equals("vibrate")) {
				this.vibrate(args.getLong(0));
			}
			else if (action.equals("alert")) {
				this.alert(args.getString(0),args.getString(1),args.getString(2), callbackId);
				PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
				r.setKeepCallback(true);
				return r;
			}

從「Notification.execute」可以得知,它同時處理了Notification中的「beep」、「vibrate」、「alert」等功能,而實際的「alert」功能實現如下:

public synchronized void alert(final String message, final String title, final String buttonLabel, final String callbackId) {

	final PhonegapActivity ctx = this.ctx;
	final Notification notification = this;
		
	Runnable runnable = new Runnable() {
		public void run() {
	
			AlertDialog.Builder dlg = new AlertDialog.Builder(ctx);
			dlg.setMessage(message);
			dlg.setTitle(title);
			dlg.setCancelable(false);
			dlg.setPositiveButton(buttonLabel, new AlertDialog.OnClickListener() {
				public void onClick(DialogInterface dialog, int which) {
					dialog.dismiss();
					notification.success(new PluginResult(PluginResult.Status.OK, 0), callbackId);
				}
			});
			dlg.create();
			dlg.show();
		};
	};
	this.ctx.runOnUiThread(runnable);
}

實際上透過Android原生的「AlertDialog」來呈現對話框的介面。

至於點擊按鈕之後的callback應用就留待有興趣的朋友自行研究了~ 不過簡單來說它就是透過前端的Ajax(採用Recursive function)不斷地向原生Java中的CallbackServer.java(ServerSocket)請求,若有資訊(callbackId等)就回傳給前端的Ajax。

2011-08-15 22:52:00 | Comments (8)

Previous Posts
Copyright (C) Ching-Shen Chen. All rights reserved.

::: 搜尋 :::

Ching-Shen Chen

::: 分類 :::

::: BloggerAds :::

::: 最新文章 :::

::: 最新回應 :::

::: 訂閱 :::

Atom feed
Atom Comment

::: 人氣指數 :::

今日人氣:865

累積人氣:1378152


::: 線上人數 :::

counter