MPEG-DASH video concept

在html5中影片播放常用方式大致上分為兩種:

  • Progress Download
  • Adaptive Streaming

Progress Download

這是一種最容易實踐的方式,主要是透過http Range的header,跟server要取需要的檔案區塊,然後存在暫存檔案。實作面只需要透過video.src或者source.src指定好來源檔案,再搭配有支援range header的server,如nginxapache等的。

Adaptive Streaming

Progress類似,最大不同會先將原先的影片切片,依照描述檔或當初切割的時間,去抓取所需要的片段。同樣可以透過http Range去針對單個檔案抓去區塊,或者直接分割成多個檔案,依照url去區分需要抓取的檔案。若在html5中,可透過MSE(Media Source Extensions),將片段資料寫入。


MPEG-DASH串流概念

MPEG-DASH (Dynamic Adaptive Streaming over HTTP)本身就是Adaptive Streaming的一種,與apple的HLS (HTTP Live Streaming)是同樣的作用。在MPEG-DASH規格中的MPD描述檔案,主要是用來描述檔案的mimeTypecodecssegment資訊等等的,MPD實際上是一個xml檔案。

在瀏覽器的實作方式,主要是透過MediaSource這個元件,建立一個sourceBuffer,接著透過MPD所描述的Initialization這個初始區塊,依照Range去向server端要取所需要的segment,最後透過sourceBuffer.appendBuffer將抓取的資料塞入,即可完成初始化。然後你只要依照時間推算出你所需要的SegmentURL區塊,用一樣的方式把資料塞回sourceBuffer.appendBuffer即可。

每一個segment的時間公式計算如下:
1
time = (size * 8) / bitrate

套用MPD中的SegmentURL

1
2
var ranges = mediaRange.split('-');
var time = (ranges[1] - ranges[0]) * 8 / bandwidth;

產生MPD格式

需要產生MPD檔,及對應的mp4,你可以透過MP4Box建立:

1
MP4Box -dash 10000 -frag 1000 -rap bunny.mp4

若是無法產出MPD檔案,代表你的mp4格式是需要經過處理,你可以透過mp4fragment去轉換:

1
mp4fragment ~/Movies/bunny.mp4 fragmented.mp4

如果你不想透過mp4fragment,你也能使用ffmpeg

1
ffmpeg -i bunny.mp4 -movflags frag_keyframe+empty_moov fragmented.mp4

在你執行完MP4Box -dash 10000 -frag 1000 -rap bunny.mp4後,應該會得到.mpdinit.mp4

1
2
├── bunny_dash.mpd
└── bunny_dashinit.mp4

產出來的xml格式會如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" minBufferTime="PT1.500S" type="static" mediaPresentationDuration="PT0H30M0.157S" maxSegmentDuration="PT0H0M10.000S" profiles="urn:mpeg:dash:profile:full:2011">
<ProgramInformation moreInformationURL="http://gpac.sourceforge.net">
<Title>bunny_dash.mpd generated by GPAC</Title>
</ProgramInformation>

<Period duration="PT0H30M0.157S">
<AdaptationSet segmentAlignment="true" maxWidth="1280" maxHeight="720" maxFrameRate="7" par="16:9" lang="und">
<ContentComponent id="1" contentType="video" />
<ContentComponent id="2" contentType="audio" />
<Representation id="1" mimeType="video/mp4" codecs="avc3.64001f,mp4a.40.2" width="1280" height="720" frameRate="7" sar="1:1" audioSamplingRate="44100" startWithSAP="1" bandwidth="400359">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>bunny_dashinit.mp4</BaseURL>
<SegmentList timescale="1000" duration="10000">
<Initialization range="0-3330"/>
<SegmentURL mediaRange="3331-826541" indexRange="3331-3482"/>
<SegmentURL mediaRange="826542-1664049" indexRange="826542-826693"/>
<SegmentURL mediaRange="1664050-2506385" indexRange="1664050-1664201"/>
<SegmentURL mediaRange="2506386-2936161" indexRange="2506386-2506489"/>
<SegmentURL mediaRange="2936162-3767192" indexRange="2936162-2936313"/>
...
</SegmentList>
</Representation>
</AdaptationSet>
</Period>
</MPD>

其中一定會使用到的有RepresentationmimeTypecodecs作為MediaSource.addSourceBuffer初始化用,而bandwidth屬性則會與SegmentURLmediaRange用來計算時間,Initialization則是剛開始初始化區需要。


在HTML5中使用MPD格式

在這邊以AJAX已經取得MPD檔案為前提來實作,主要都是片段程式碼,若你要直接看完整範例,可直接參考微軟的MPEG-DASH實作範例

首先需要先建立MediaSource元件:

1
var mediaSource = new MediaSource();

接著透過URL.createObjectURL,將URL指定給video.src

1
videoElement.src = URL.createObjectURL(mediaSource);

註冊sourceopen事件,對sourceBuffer做初始化:

1
2
3
4
5
6
7
8
9
mediaSource.addEventListener('sourceopen', function (e) {
try {
videoSource = mediaSource.addSourceBuffer(`${mimeType} codecs="${codecs}"`);
initVideo();
} catch (e) {
log('Exception calling addSourceBuffer for video', e);
return;
}
},false);

參照MPD檔案中,抓取Initialization裡面的range屬性,向server要取初始化的區塊,再塞入到buffer之中:

1
2
3
4
function initVideo() {
var range = Initialization.getAttribute('range');
fetchRange(range);
}

fetchRange的function中,主要是透過http的range header,向server取得所需要的區塊,再塞入到buffer之中,換句話說,你只要載入Initializationrange區塊後,即可載入你所需要的SegmentURL片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function fetchRange(range) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
var url = 'http://localhost/bunny_dashinit.mp4';

xhr.open('GET', url);
xhr.setRequestHeader("Range", "bytes=" + range);
xhr.send();
xhr.responseType = 'arraybuffer';

xhr.addEventListener("readystatechange", function () {
if (xhr.readyState == xhr.DONE) { //wait for video to load
try {
videoSource.appendBuffer(new Uint8Array(xhr.response));
resolve();
} catch (e) {
log('Exception while appending', e);
reject();
}
}
}, false);
});
}

若你不想透過range header,向server取得資料,你也可以直切分割成多個檔案,直接透過url的方式指定:

1
MP4Box -dash 4000 -frag 4000  -rap -segment-name output/segment_ bunny.mp4

透過上面指令,最後你應該會取得到多個分割檔案,及segment_init和bunny_dash.mpd檔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
├── bunny.mp4
├── bunny_dash.mpd
└── output
├── segment_1.m4s
├── segment_10.m4s
├── segment_100.m4s
├── segment_101.m4s
├── segment_102.m4s
├── segment_103.m4s
├── segment_104.m4s
├── segment_105.m4s
├── segment_106.m4s
├── segment_107.m4s
├── segment_108.m4s
├── segment_109.m4s
├── segment_11.m4s
├── segment_110.m4s
├── segment_111.m4s
├── segment_112.m4s
├── segment_113.m4s
├── segment_114.m4s
...
└── segment_init.mp4

若是針對上面檔案沒做處理,或者沒依照SegmentURLrange,直接塞入MediaSource中的sourceBuffer,你會發現MediaSource將會被abort。按照原先mp4檔案,你仍然可透過range,抓取部分區塊,最後組成一個blob檔案,透過URL.createObjectURL,設定到video.src,這時你會發現,你有抓取的區塊是可以播放。


下載相關執行檔


相關文章

Image converter

在很多情況要顯示圖片清單時,都會需要固定的圖片size,如果想保留原始圖片,又想要有固定size的大小,大部份都會採用overflow的方式,將不需要的地方隱藏起來,或者是直接使用瀑布流的方式,這樣就不需要理會圖片高度,只要設定一個固定寬度即可,但是有時候的圖片,可能會過於龐大,所以需要裁剪適當的小,這時候可能就需要自動截取適當的大小。

Quick Start

imageConverter

LiveDemo

imageConverter

Install

使用bower安裝。

1
bower install imageConverter

Usage

截取圖片200x150,如果圖片比這size還要小,同樣也能截取出來,只是圖片可能會變成模糊不清。

1
2
3
4
5
6
7
8
var im = new Image();
im.src = "pig_head.png";

imageConverter.resizeAndOptimize( im, 200 ,150, function( data ){
var outIm = new Image();
outIm.src = data;
document.body.appendChild( outIm );
});

以上範例為js版的imageConverter,若要使用後端處理,也可透過python版本的imageConverter

The unsafe resource URL and cross origin on angular

在angular很常遇到compile template時出錯的情況,因為在angular中,使用比較嚴謹的判斷方式,只要看到不符合規範,會直接拋出exception

##Cross origin

以下是一個video的範例,設定影片的source:

1
2
3
<video width="320" height="240" controls>
<source src="{{source}}" type="video/mp4">
</video>

設定source的影片來源:

1
$scope.source = "http://other.sparrowhome.twbbs.org/movie.mp4"

這時就會發現angular拋出exception,出現cross domain的問題。

但是若是直接寫在html上呢?

1
2
3
<video width="320" height="240" controls>
<source src="http://other.sparrowhome.twbbs.org/movie.mp4" type="video/mp4">
</video>

此時你可以發現能正常的播放,但是在angular就是會拋錯,這時你可以透過$sceDelegateProvider,來設定黑名單白名單,決定哪個domain可以讀取這隻影片。

假設你要讓所有domain都能使用,在白名單可以這樣寫:

1
2
3
4
5
angular
.module('MyApp')
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['**']);
})

若是你只要目前網域底下的子網域都可以讀取的話,可以這樣寫:

1
$sceDelegateProvider.resourceUrlWhitelist(['http://*.sparrowhome.twbbs.org'])

如果要透過黑名單可以直接使用resourceUrlBlacklist

##Unsafe resource URL

如果是讀取img,不管是cross domain都能正常讀取,不過還是會有不讀取的狀況。

例如你透過一個<input type="file">,去取得顯示image,像是<img ng-src="{{img}}" />

1
$scope.img = URL.createObjectURL(imgFile);

此時就會拋出unsafe:blobexception,依據URL.createObjectURL所取得url,可以很容易發現,最前方協定是不同的。例如URL.createObjectURL回傳一個blob:http%3A%2F%2Fsparrowhome.twbbs.org%2Fimage.png,此時不在預設的允許名單中,這時就要透過$compileProvider.imgSrcSanitizationWhitelist去改寫。

像是讓所有協定都通過:

1
2
3
4
angular.module('MyApp').config(['$sceDelegateProvider', function( $compileProvider ) {

$compileProvider.imgSrcSanitizationWhitelist(/^.*/);
}

也可自訂要通過特定的協定,像是/^(https?|ftp|file|blob|data):/,同樣的href也有aHrefSanitizationWhitelist可以去設定。在angular中使用要嚴謹的判斷方式,當然也可透過jquery或原生javascript繞過這問題,不過這樣就有點失去用angular的意義了,畢竟用template的方式,比起寫code跟為簡潔。

The ngAnimate on angular(1.2.13)

angular的ngAnimate主要是依照不同的Directive,對照不同的class做切換,以下有一張官方提供的對照表:

Directive Supported Animations
ngRepeat enter,leave and move
ngView enter and leave
ngInclude enter and leave
ngSwitch enter and leave
ngIf enter and leave
ngClass add and remove
ngShow & ngHide add and remove (the ng-hide class value)

比較值得注意的是,angular(1.2)以上的版本,是依據directive的目前的status,替換不同的class,它會在每個不同的status加上ng-....的class,如ng-enterng-leave等的。

ngRepeat為例,定義一個html結構:

1
2
3
4
5
<ul>
<li ng-repeat="role in roles" class="demo">
{{role}}
</li>
</ul>

加入一個值到roles

1
$scope.roles.push( "programer" );

則此時會先插入一個ng-enterclass,做初始配置用,接著在插入ng-enter-active的class,改變後的狀態,只有設定transition,這時就會產生動畫效果。

依照element的class名稱,css的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.demo.ng-enter {

-webkit-transition: all 1s linear;
transition: all 1s linear;

/**
* set styles
*/

}
.demo.ng-enter-active {
/**
* set styles
*/

}

若是不支援css3的瀏覽器,則必須自己實作動畫效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
app.animation('.demo', function() {

return {

enter:function( element, done ){

doEffect( done );

return function( cancelled ) {

if ( cancelled ) {

//stopEffect();

} else {

//completeTheAnimation();
}

};
}

};

});

如果要使用到jquery來做特效,需在angular載入之前,先行載入jquery,因為angular會判斷要使用jqueryjqlite,然後再做一些處理。若要使用jquery改變顏色,則需要額外載入jquery-color。另外如果同時用javascript和css實作特效,則css特效會被javascript所覆蓋。

Example

Install

1
bower install angular angular-animate jquery jquery-color

Create a angular app and load a ngAnimate module( app.js ):

1
var app = angular.module('angularNgApp', ["ngAnimate"]);

html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body ng-app="angularNgApp">

<div ng-controller="MainCtrl">
<ul>
<li ng-repeat="thing in awesomeThings" class="demo">
{{thing}}
</li>
</ul>
</div>

<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->



<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/jquery-color/jquery.color.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- endbower -->

<script src="app.js"></script>
<script src="controller.js"></script>
<!--[if lt IE 9]>
<script src="animation.js"></script>
<![endif]-->


</body>
</html>

main.css:

1
2
3
4
5
6
7
8
9
10
.demo.ng-enter {

-webkit-transition: all 1s linear;
transition: all 1s linear;
background: #000;
}

.demo.ng-enter-active {
background: #fc3;
}

controller.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.controller('MainCtrl', function ( $scope, $timeout ) {

$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];

$timeout( function(){

$scope.awesomeThings.push("123");

}, 2200);

});

animation.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
app.animation('.demo', function() {

return {
enter : function( element, done ) {

element.animate({
'background-color': 'red'
}, done);

return function( cancelled ) {

if(cancelled) {
//stopTheAnimation();
}
else {
//completeTheAnimation();
}
}
}
};
});

Capture a image block on angular

最近需要使用到一個圖片切割的工具,在angular的package中找了一下,沒有找到比較合適,因為只需要支援html5的瀏覽器,就實作了一個間單的工具(ngImageEditor)。

以下是demo實際執行的畫面:

explain

使用與安裝:

Demo

demo

Install

1
bower install ngImageEditor

Support

  • IE9+
  • chrome
  • firefox

設定editor的attributes

1
<div img-src="imgSrc" ng-image-editor="imageEditor" selected="selected"></div>

載入要切割的圖片

1
$scope.imgSrc='/images/head.jpeg';

設定切割的區塊位置及大小

1
$scope.selected = {width:150,height:150,top:0,left:0};

將區塊大小的image取出

1
$scope.imageEditor.toDataURL();

實作方式主要是透過canvas作轉換,至於cross domain,必須是同個domain,否則要從server上設定,允許哪個domain可以存取作編輯。

在server上設定你要跨網域的domain:

1
Access-Control-Allow-Origin: http://your.domain.com

在javascript中呼叫img.src之前,加入以下設定:

1
img.crossOrigin = "Anonymous";

可參考這篇

jquery detach on angular directive

在angular中會有基本的jquery-lite,也就是angular.element,當在include angular之前,就先include jquery,此時angular的jquery-lite,就會被自動替換成jquery。

替換angular.element的source code如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
// Method signature:
// jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
jqLitePatchJQueryRemove('remove', true, true, false);
jqLitePatchJQueryRemove('empty', false, false, false);
jqLitePatchJQueryRemove('html', false, false, true);
} else {
jqLite = JQLite;
}
angular.element = jqLite;
}

另外一個需要注意的地方就是jqLitePatchJQueryRemove,會將jquery原生的function,多經過一層處理,處理完在呼叫jquery的function

jqLitePatchJQueryRemove的source如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) {     
var originalJqFn = jQuery.fn[name];
originalJqFn = originalJqFn.$original || originalJqFn;
removePatch.$original = originalJqFn;
jQuery.fn[name] = removePatch;

function removePatch(param) {
// jshint -W040
var list = filterElems && param ? [this.filter(param)] : [this],
fireEvent = dispatchThis,
set, setIndex, setLength,
element, childIndex, childLength, children;

if (!getterIfNoArguments || param != null) {
while(list.length) {
set = list.shift();
for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
element = jqLite(set[setIndex]);
if (fireEvent) {
element.triggerHandler('$destroy');
} else {
fireEvent = !fireEvent;
}
for(childIndex = 0, childLength = (children = element.children()).length;
childIndex < childLength;
childIndex++) {
list.push(jQuery(children[childIndex]));
}
}
}
}
return originalJqFn.apply(this, arguments);
}
}

其中element.triggerHandler('$destroy')會告知angular底下的transcludedScope,這個element已經被摧毀了,所以會導致,$element.detach的時候,transcludedScope無法被更新。element.triggerHandler('$destroy')的監聽event可以參考createBoundTranscludeFn這個function,會有下這段code去監聽:

1
clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));

jqLitePatchJQueryRemove只替換removeemptyhtml,而沒有替換detach,使用detach卻會影響scope,真正原因要找jquery的source code。

因為detach間接呼叫了remove:

1
2
3
detach: function( selector ) {
return this.remove( selector, true );
}

Example(1.2.7):

由此可知,只要scope尚未ready時,呼叫detach就不會中斷scope的更新。若要讓directive的scope正常運作,可用以下方式:

  1. 不使用transclude
  2. 不在scope ready中,使用detach
  3. 使用原生api
  4. 最後一種方式,jquery在angular之後include

如果不是這麼需要使用到jquery,建議就不要include了,當然jquery也提供很多方便的功能,依照project需求自己評估吧!

Copy canvas on html5

要複製整張canvas,可以使用getImageDataputImageData。透過getImageData這個method,將整張canvas的資料取出來,在經由putImageData將資料覆蓋上去。

Copy:

取得整張canvas的data:

1
2
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height );

在將imageData的資料,塞到copyCanvas對應的data區塊:(在這預設兩張canvas大小都相同)

1
2
var cctx = copyCanvas.getContext("2d");
cctx.putImageData( imageData, 0 ,0 );

這樣你就會看到第一張的canvas,完整的copy到第二張上面。

Example:

最初的兩張canvas

style

1
2
3
canvas {                     
border:1px solid gray;
}

html

1
2
<canvas id="main" width="300" height="200"></canvas>
<canvas id="copy" width="300" height="200"></canvas>

javascript

1
2
3
4
5
6
7
8
9
10
11
//left canvas
var canvas = document.querySelector( "#main" );
var ctx = canvas.getContext("2d");
ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,150,75);

//right canvas
var copy = document.querySelector( "#copy" );
var cctx = copy.getContext("2d");
cctx.fillStyle="#FF00FF";
cctx.fillRect(20,20,150,75);
  • 把左邊的canvas透過drawImage畫到右邊的canvas:

    1
    cctx.drawImage( canvas, 0 ,0 );

    透過drawImage畫到右邊canvas

  • 將左邊的canvas的資料,透過putImageData取代右邊canvas資料:

    1
    2
    var imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height );
    cctx.putImageData( imageData, 0 ,0 );

    透過putImageData畫到右邊canvas

如果使用drawImage,透明部份將不會畫上去,沒辦法完整複製,而putImageData,則是完全取代某區塊的的bytes。

angularjs timepicker on bootstrap3

angularjs的ui-bootstrap,套用至bootstrap3,目前只能支援部份的元件。所以就自己實作了一個timepicker的元件,如下:

Dependency

  • boostrap3
  • angularjs

Support

  • IE8+
  • chrome
  • firefox

Install

用bower install

1
bower install ngTimepickerForBs3

Usage

include所需要的scripts和styles:

1
2
<link rel="stylesheet" href="bower_components/ngTimepickerForBs3/angularjs-timepicker.css"/>
<script type="text/javascript" src="bower_components/ngTimepickerForBs3/angularjs-timepicker.js"></script>

設定timepicker的model和初始值;

1
<div ng-timepicker-for-bs3 ng-model="test" ng-init="test={hours:23,mins:59}"></div>

載入ngTimepickerForBs3的module:

1
2
3
angular.module( "app", [
"ngTimepickerForBs3"
]);

在Titanium HTTPClient使用cookie與header

titanium內建的HTTPClient本身就有實作cookie的功能,使用起來跟瀏覽器一樣,會自動解析response header,及發送以設定的cookie,而不需要額外去解析header,和添加request的header,要清除cookie可以使用clearCookies,若要在client端設定cookie,可以使用setRequestHeader

清除與設定cookie

建立一個HTTPClient:

1
var client = Ti.Network.createHTTPClient({});

指定清除某個網域底下的cookie:

1
client.clearCookies( "www.example.com" );

由client設定cookie:

1
client.setRequestHeader( "cookie", "name=sparrow;" );

使用cookie常見問題

通常cookie能不能被存取,會依照expires、path、domain、HttpOnly、secure這幾項辨識:

  • expires : cookie的時效性,當時間超過expires設定的日期,這個cookie將會被捨棄。

    1
    expires=Sat, 17-Oct-2015 14:11:09 GMT;
  • path : 在domain下,cookie允許存取的路徑。

    1
    path=/;
  • domain : cookie允許存取的domain。

    1
    2
    3
    4
    domain=.google.com.tw;
    最前面的.指得是子網域皆可存取,例如:
    www.google.com.tw
    ad.google.com.tw
  • HttpOnly : 在瀏覽器下看到這個屬性時,只允許server端操作此cookie,換句話說client雖然存此cookie,卻無權操作,可防止透過xss將cookie偷走。

    1
    HttpOnly
  • secure :只允許在https上操作此cookie,在一般http接無法存取。

    1
    secure;

如果有用到nginx、apache這類的server去做proxy,需要檢查path或domain,才不會遇到cookie無效的狀況。

解析response的header

在android的device,如果想要取得header的內容可以使用allResponseHeaders,雖然titanium有提供getResponseHeader的功能,實際使用過都無法取得到值。

在onload取值:

1
2
3
4
client.onload = function(){

Ti.API.info( client.allResponseHeaders );
};

如果你想取得一個header的json,可以實作一個類似這種function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

/**
* @param {*} ary
* @type boolean
*/
var isArray = function( ary ){

return Object.prototype.toString.call( ary ) === "[object Array]";
};

/**
* @param {String} allResHeaders
* @type Object
*/
var getHeaders = function( allResHeaders ){

var result = {},

headerStrs = allResHeaders.split( "\n" );

for ( var index in headerStrs ) {

if ( headerStrs[index] ) {

var header = result[index].split( ":", 2 ),

name = header[0].toLowerCase(),

value = header[1];

if ( result[name] && !isArray( result[name] ) ) result[name] = [ result[name], value];

else if ( isArray( result[name] ) ) result[name].push( value );

else result[name] = value;

}
}

return result;
};

透過getHeaders取值:

1
2
var headers = getHeaders( client.allResponseHeaders );
Ti.API.info( headers );

Angularjs directive uses transclude attribute

使用directive時,會將符合restrict的element都做初始化,directive指向的element包含child-element,這時候就要設定transclude:true,將child-element,添加到template裡,可以使用ng-transclude或者透過directive定義controller去處理。

透過ng-transclude處裡child element

定義好tag的結構,button-group為預設tag:

1
2
3
4
5
<div>
<button-group>
<button>Enter</button>
</button-group>
</div>

透過ng-transclude,將原先在button-group裡的child-element添加至ng-transclude裡面:

1
2
3
4
5
6
7
8
9
10
11
app.directive( "buttonGroup", function(){

return {

restrict: 'EA',
template: '<div class="btn-group" ng-transclude ></div>',
replace: true,
transclude: true
};

});

最後就會取得到底下的結果:

1
2
3
4
5
<div>
<div class="btn-group" ng-transclude="">
<button class="ng-scope">Enter</button>
</div>
</div>

透過directive的controller添加child element

使用$transclude取得clone的element,在append至需要添加的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
controller: ['$scope', '$element', '$transclude', function ($scope, $element, $transclude) {

$transclude(function(clone) {

var primaryBlock = $element.find('div.primary-block');
var transcludedButtons = clone.filter(':button');

angular.forEach(transcludedButtons, function(e) {

if (angular.element(e).hasClass('primary')) {
primaryBlock.append(e);
}

});
});
}]

使用ngTransclude的Demo

完整的範例,請參考jsfiddle上的code。