2020年3月26日 星期四
dart 程式庫(package)管理
dart 使用檔案 pubspec.yaml 指引程式庫(package)的運作邏輯, pubspec.yaml 裡面最重要的是 name: 指引,用來標示專案的名稱,同時也代表該專案程式庫(package)所處最上層路徑, 將專案名字寫在檔案裡面的好處是:若將最上層目錄搬移或更名也不影響程式的運作. 其次是 dependencies: 區塊指引,用來紀錄該專案與外部程式庫之相依性, 相關的程式庫須用 tab 鍵或是空白鍵先縮排(indent),再使用"名稱:版本"的格式一一分行列表, 另外可以用符號 # 當註解,例如:
#pubspec.yaml
name: myPackage
dependencies:
ffi: 0.1.3 #外部程式庫 ffi, 需求版本 0.1.3
crypto: 2.1.4 #外部程式庫 crypto, 需求版本 2.1.4
開啟終端機, 執行 pub get 後就會根據 pubspec.yaml 指引去安裝(或下載)好相關的程式庫,安裝好的結果會儲存在檔案 .packages 裡面. 整個專案目錄結構示範如下:
myPackageRoot - #專案根目錄
lib - #內定專屬程式庫(package)目錄起點
maintain.dart #程式庫維護原始碼
src - #專案其它程式原始碼目錄
utility.dart
help.dart
main.dart #專案主程式原始碼
內定專屬目錄 lib 會被當成是專案外部程式庫的參考起點,通常在 dart 代碼若是一開頭使用 import 'package: ...' 或是 export '...' 時, 就會以 lib 目錄當參考點去引入相關的檔案,因此可以準備一個檔案例如放在 lib/maintain.dart 專門當作程式庫維護程式碼, 在裡面用 export 方式間接引入檔案或是用 import 方式直接引入各項程式庫. 通常習慣是在 lib 目錄下再新增一個目錄名稱,例如 src, 把該專案的其餘程式全放在 lib/src 目錄底下(如上述範例)當成該專案的專屬程式庫, 未來只要將該目錄(src)複製到其他專案裡面, 就能重複使用此專屬程式庫,甚至萬一把程式庫目錄 src 改名了, 也只要修改維護程式內的相對目錄 src 就可. 總之, 目錄結構更動是維護程式碼的主要任務,盡可能不影響程式庫的運作邏輯:
// maintain.dart
export 'src/utility.dart'; // 間接從路徑 lib/src 引入檔案 utility.dart
export 'src/help.dart'; // 間接從路徑 lib/src 引入檔案 help.dart
// import 'dart:html';// 直接引入 dart 核心程式庫 html
// ...
經過上述規劃後, 主程式只要將 maintain.dart 引入就可, 乾淨俐落, 但須注意的是, 當要引入該專案的專屬程式庫(package)時, 須先指明專案名稱, 換句話說專案名稱也同時身兼專案專屬程式庫的角色:
// main.dart
import 'package:myPackage/maintain.dart'; //直接從 myPackage 引入 lib/maintain.dart
main( ) {
// ...
}
2020年3月25日 星期三
從 dart 呼叫 javascript
參考官網文章: http://dartdoc.takyam.com/articles/js-dart-interop/
// index.html
<html><head><meta charset='utf-8'></head><body>
<script type='application/javascript' src='hello.js'></script>
<script type='application/javascript' src='main.js'></script>
</body></html>
// hello.js
var greeting="Hello"; // 1. variable
function fun( ){ //2. function
console.log("How are you?");
}
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
function rectangle(width, height) { // 3. object
return new Rectangle(width, height); // return an object
}
// main.dart
import 'dart:js';
void main() {
var greeting = context['greeting']; // 1.
print("$greeting");
context.callMethod('fun'); //2.
final rectangle = JsObject(context['rectangle'], [5, 1]); //3.
print(rectangle['width']);
}
2020年3月23日 星期一
dart 設定好開發 javascript 的工作環境: dart2js
2. 開啟專案的目錄
3. 設定好執行 debug 的環境, 準備讓 chrome 開啟 index.html 檔案, 只要點選 vscode 的選單 Debug -> Open Configurations, 在開啟 launch.json 後, 填入以下文字, 主要是填入讓 chrome 開啟 url 檔案所在位置, 並設定好工作的根目錄:
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "執行 Chrome 開啟 index.html 檔",
"url": "file:///${workspaceFolder}/index.html",
"webRoot": "${workspaceFolder}"
}
]
}
4. 設定好將 dart2js 的環境, 準備將 dart 語法轉換為 javascript, 只要點選 vscode 的選單 Terminal -> Configure Tasks, 在開啟 tasks.json 後, 填入以下文字, 主要是將 dart2js 執行檔的絕對路徑填入 "command" 所指示的位置, 並設定好一些必要參數 args:
{
"version": "2.0.0",
"tasks": [
{
"label": "dart2js",
"type": "shell",
"command": "/home/mint/Downloads/flutter/bin/cache/dart-sdk/bin/dart2js",
"args": [
"${file}",
"-o",
"${fileBasenameNoExtension}.js"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
5. 撰寫 index.html 要讓 Chrome開啟 url 後, 自動執行經 dart2js 轉換後的 main.js:
<html><head><meta charset='utf-8'></head><body>
<script type='application/javascript' src='main.js'></script>
</body></html>
6. 用 dart 語言在 html 的 canvas 上繪製 sin(2πft) 函數圖, 範例主程式 main.dart:
// main.dart
class CanvasSin {
final DivElement div = document.createElement('div');
final CanvasElement canvas = document.createElement('canvas');
final int width, height;
get fullYscale => height~/2;
CanvasRenderingContext2D get ctx => canvas.getContext("2d");
void penline(int x1, int y1, int x2, int y2) => ctx .. strokeStyle="red" ..
beginPath( ) .. moveTo(x1, y1) .. lineTo(x2, y2) .. stroke( );
void get penOrigin => ctx .. save( ) .. translate(0, fullYscale) .. scale(1, -1) ..
beginPath( ) .. moveTo(0, 0);
void get penFinish => ctx .. strokeStyle="green" .. stroke( ) .. restore( );
void penTo(int x, double y) => ctx.lineTo(x, y);
CanvasSin(this.width, this.height) {
document.body.append(div);
div.append(canvas);
canvas.style.border = "1px solid";//外框 1 點
canvas.width = width;
canvas.height = height;
penline(0, 0, 0, height);// y-axis
penline(0, fullYscale, width, fullYscale);// x-axis
int freq = 2;
double f(int t) => fullYscale * sin(2.0*3.14159*freq*t/width);// sin(2πft)
penOrigin;
for(int t = 0; t < width; t ++) penTo(t, f(t));
penFinish;
}
}
void main( ) => CanvasSin(800, 600);
7.目前 dart2js 僅能編譯整個內含 main( ) 主程序的 dart 原始碼, 無法針對個別單一程式碼去編譯, 經上述步驟 4 設定好後, 要將目前用 dart 語言編輯的主程式轉換為相對應的 javascript 程式碼, 只要按下快捷鍵 ctrl-shift-B, 或是用選單 Terminal -> Run Task 選取 dart2js 直接編譯
8. 經上述步驟 1~6 後, 按下快捷鍵 F5, 或是用選單 Debug -> Start Debugging 就能讓 Chrome 直接開啟 url 執行所轉換後的 javascript
2020年3月20日 星期五
用 dart 語言簡單驗證 ECDSA 演算法
// main.dart to verify ECDSA
import 'dart:math';
BigInt random(BigInt m) {
BigInt temp = BigInt.from(0);
int bits = m.bitLength;
while (bits > 0) {
temp = temp << 8 | BigInt.from(Random(DateTime.now( ).microsecondsSinceEpoch).nextInt(255));
bits -= 8;
}
return temp % m;
}
extension Divider4ECDSA on BigInt { // a method .__ to operate / % m
BigInt __(BigInt divisor, BigInt m) => this * divisor.modInverse(m) % m;
}
class ModField {
BigInt _value;
BigInt get value => _value;
int get mostbits => _value.bitLength - 1;
static BigInt _prime;
BigInt get p {
if(_prime == null) _prime = BigInt.parse("115792089237316195423570985008687907853269984665640564039457584007908834671663");
return _prime;
}
static BigInt parse(var temp) =>
temp is ModField ? temp._value :
temp is String ? BigInt.parse(temp):
temp is num ? BigInt.from(temp) :
temp is BigInt ? temp : BigInt.zero;
bool testbit(int k) => _value >> k & BigInt.one == BigInt.one;
bool operator ==(var other) => _value == parse(other)
bool operator < (var other) => _value < parse(other);
bool operator > (var other) => _value > parse(other);
ModField operator ^ (int exponent) => ModField(_value.pow(exponent));
ModField operator + (var other) => ModField(_value + parse(other));
ModField operator - (var other) => ModField(_value - parse(other));
ModField operator * (var other) => ModField(_value * parse(other));
ModField operator %(var other) => ModField(_value % parse(other));
ModField operator / (var other) => ModField(_value*parse(other).modInverse(p));
ModField (var temp) {
_value = parse(temp) % p;
if (_value < BigInt.zero) _value = _value + p;
}
}
class Secp256k1 { // y^2 = x^3 + 7 mod p, ECC order number= GFn
ModField n, x, y;
bool get isPoint => y^2 == (x^3) + 7;
Secp256k1(var temp) {
if (temp is Secp256k1) { // copy constructor
n = temp.n;
x = temp.x;
y = temp.y;
} else {
n = ModField(temp);
x = ModField("55066263022277343669578718895168534326250603453777594175500187360389116729240");
y = ModField("32670510020758816978083085130507043184471273380659243275938904335757337482424");
if(n > 1) {
_selfMul(n);// 1 Gn(x, y)
n = ModField(1);
}
}
}
Secp256k1._point(gx, gy) {
n = ModField(1);
x = ModField(gx);
y = ModField(gy);
}
factory Secp256k1.G([var gx=0, var gy = 0]) {
if(gy == 0) return Secp256k1(1);
final temp = Secp256k1._point(gx, gy);
return temp.isPoint ? temp : Secp256k1(1);
}
Secp256k1 get _selfX2 { // point doubler
final px = x;
final s = (x^2) * 3 / (y*2); // s = 3*x*x / 2y
x = (s^2) - x*2; // x = s*s - 2x
y = s * (px - x) - y; // y = s * (px - x) - py
return this;
}
Secp256k1 _selfAdd(ModField gx, ModField gy, {gn = 1}) { // point adder
if (x == gx) n = ModField(0);
else if (n == 0) {
x = gx;
y = gy;
n = ModField(gn);
} else {
final s = (y - gy) / (x - gx);
x = (s^2) - x - gx; // s*s - x - gx
y = s*(gx - x) - gy; // s(gx -x) - gy
}
return this;
}
Secp256k1 _selfMul(ModField multiplier) {// point scale by DAA
if (multiplier > 1) {
var bits = multiplier.mostbits;
final gx = x;
final gy = y;
while (bits-- > 0) {
_selfX2;
if (multiplier.testbit(bits)) _selfAdd(gx, gy);
}
}
return this;
}
get dump => print("${n.value} G(${x.value},${y.value})");
static BigInt _order;
get GFn {
if(_order == null) _order = BigInt.parse("115792089237316195423570985008687907852837564279074904382605163141518161494337");
return _order;
}
operator + (Secp256k1 Q) => Secp256k1.G(x, y)._selfAdd(Q.x, Q.y);
operator * (var scale) => Secp256k1.G(x, y)._selfMul(ModField(scale));
List<BigInt> signECDSA(BigInt d, BigInt z) { // use key d to sign z, % use GFn
final BigInt k = random(GFn);
final BigInt r = Secp256k1(k).x.value; // r = G.x
final BigInt s = (z + r * d) .__ (k, GFn); // s = (z + r * d)/k % GFn
return [z, r, s];
}
bool checkECDSA(List<BigInt> signature) {
if(signature.length < 3) return false;
final BigInt z = signature[0];
final BigInt r = signature[1];
final BigInt s = signature[2];
final BigInt u = z .__ (s, GFn); // u = z/s % GFn;
final BigInt v = r .__ (s, GFn); // v = z/s % GFn;
final Secp256k1 R = Secp256k1.G( )*u + Secp256k1.G(x, y)*v;
return R.x == r;
}
}
main( ){
final text = BigInt.from(123);// 待簽字
final secret = BigInt.from(123456789); // 私鑰
final qPublic = Secp256k1(secret); // 公鑰
final signature = qPublic.signECDSA(secret, text);// 用 ECDSA 簽證文字產生特徵碼
qPublic.dump;
print( qPublic.isPoint);
print(qPublic.checkECDSA(signature) ? "OK": "Fake!");// 用公鑰驗證特徵碼
}
執行 dart main.dart 看結果:
1 G(4051293998585674784991639592782214972820158391371785981004352359465450369227 ,
88166831356626186178414913298033275054086243781277878360288998796587140930350)
true
OK
2020年3月16日 星期一
用 dart 語言產生一個簡單的區塊鏈
// pubspec.yaml
name: blockchain
version: 0.0.1
description: >-
A super simple example of blockchain
author: masontseng
environment:
sdk: '>=2.6.0 <3.0.0'
dependencies:
crypto: ^2.1.4
編輯主程式 main.dart, 接著開啟終端機執行 dart main.dart 看執行結果:
// main.dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
class Block {
final int nonce, index, timestamp;
final String data, pre_hash;
get plain => "${nonce}-${index}-${data}-${timestamp}-[${pre_hash}]";
get hash => sha256.convert(utf8.encode(plain)).toString( );
get dump => print("${plain} [${hash}]");
factory Block.genesis( ) => Block(0, 0, "genesisBlock", 1584332296607000, "0");
Block(this.nonce, this.index, this.data, this.timestamp, this.pre_hash);
}
class BlockChain {
var field = [ ]; //field of blockchain is a matrix
set blockchain(var temp) {
if(temp.length <= field.length) return;
if(temp[0].data != field[0].data) return;
for (var i = 1; i < field.length; i++) if (! isBlockValid(temp[i], field[i - 1])) return;
for (var i = field.length; i < temp.length; i++) if (! isBlockValid(temp[i], temp[i - 1])) return;
field = temp;
}
get blockchain => field;
get dump { for (var i = 0; i < field.length; i++) field[i].dump; }
Block powMineBlock(String eData, {int nonce=0}){
final lastblock = field[field.length - 1];
final timeNow = DateTime.now( ).microsecondsSinceEpoch;
return Block(nonce, lastblock.index + 1, eData, timeNow, lastblock.hash);
}
void addBlock(String e) => field.add(powMineBlock(e, nonce: 1));
bool isBlockValid(Block block, Block pre) =>
block.index == pre.index + 1 &&
block.pre_hash == pre.hash
bool isValid( ) {
for (var i = 1; i < field.length; i++) if (! isBlockValid(field[i], field[i - 1])) return false;
return true;
}
BlockChain( ) { field.add(Block.genesis( )); }
}
main( ){
final c = BlockChain( );
for(int i=1; i<10; i++) c.addBlock("extendBlock${i}");
c.dump;
print(c.isValid( ));
final d = BlockChain( );
d.blockchain = c.blockchain;
d.dump;
print(d.isValid( ));
}
後記:
1. 理解 proof of work (pow): 是一個群體共識決(交易方法),他是針對一個數學問題,很容易驗證但很難找到答案,或許只能用嘗試錯誤法,有點類似樂透的方式猜出答案,但不管如何,只要能解答讓大部分的人同意並形成的共識決,最後將結果紀錄於區塊鏈內,解題者就可獲取報酬,缺點是要浪費電腦的計算力
2. 理解 proof of stake (pos): 也是一個群體共識決(交易方法),交易發行者針對交易行為事先在網路上廣播,擬參與交易認證者事先以一定比例(賭注/持有者資金)資金注入系統,該筆資金將在網路中暫時封鎖無法交易,有點類似價金履約保證的概念當作履約保證金,此封鎖的資金稱為 stake,擬入區塊鏈的區塊就由該履約保證者提出並上呈網路供公眾確認.最後一旦交易完成並進入區塊鏈,提出履約保證者就能獲得一定比例的報酬作為手續費,封鎖的資金也同時解凍,但若違反規則,也會獲得懲罰,像是沒收保證金等等. pos 解決了耗費電腦運算的麻煩,但隨之而來的是要解決 nas(Nothing At Stake)的問題.
2020年3月14日 星期六
關於武漢肺炎
近日中國官方甚至丟出陰謀論說武漢肺炎來自美國,中國共產黨真是無恥到極點.要知道唯有中國共產黨才會一心一意想要洗清歷史罪名,就好比當年文化大革命般,實際卻是對歷史鬥爭將黑的歷史說成白的,扭轉人民的思想,再譬如美其名的新疆再教育營,實際卻是一個思想改造大集中營,另外還有中國共產黨在1989年6月4日天安門大規模用機關槍及坦克屠殺學生的事件歷歷在目,但對中國內部卻直接將該段歷史抹除,如此般的玩弄文字遊戲,美化及合理化其所作所為,企圖就是要讓人民忘記中國共產黨所造就的人類浩劫,竄改歷史就是不想反省之前犯下的罪過,只要將壞事從歷史上消除,讓人以為世界太平,這才是它一貫的技倆與目的,居住在中國大陸的年輕人,現恐怕早被洗腦對該#8964事件完全沒有任何記憶.這就是中國共產黨最厲害的地方,它讓你對他的行為完全無任何警覺,私底下在中國內部只要有不利的中國的言論,不斷用另外正向力量來合理化其殘酷的行為,宣傳形成主流意識,對於不當言論一律將它抹除,目的是箝制中國大陸內部的言論自由,讓人民只能相信政府的所作所為,這就夠了,對於世界如何看待中國或是中國共產黨根本無所謂,也無視一個人類該有的言論自由等等的基本人權,導致事實無法忠實呈現,疫情也不誠實申報,讓世界摸不著頭緒,錯失防堵契機
因此我們要追本溯源給歷史一個真相,中國共產黨箝制言論與隱匿疫情才是造成武漢肺炎世界大流行的罪魁禍首,而 WHO 雖未認清事實真相,但其領導人的消極作為與鄉愿行為卻成為幕後最大推手, 武漢病毒的來源的的確確來自中國武漢地區,一些坊間的報導透漏很可能是在武漢P4實驗室所製造出來,極有可能是管理不慎造成病毒洩漏,從中國軍方接管武漢P4實驗室來看,就能嗅出一些蛛絲馬跡,也是最合理的解釋.
2020年3月4日 星期三
用 grub2 boot loader 啟動 LMDE4
menuentry "Boot LMDE4 iso" {
set root=(hd0,2)
set iso=/boot/lmde4.iso
loopback loop $iso
linux (loop)/live/vmlinuz boot=live findiso=$iso
initrd (loop)/live/initrd.lz
}
2020年2月19日 星期三
dart 使用 ffi 和 c 語言溝通
1. 使用 linux 作業系統, 安裝好dart sdk , c 語言編譯程式加上 cmake
2. 新增一個 dart 專案, 專案內新增一個 hello 目錄 , 進入 hello 目錄, 準備要編譯成 hello 模組(libhello.so):
2.1 先在 hello 目錄裡面編輯一個檔案 CMakeLists.txt:
cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(hello_library VERSION 1.0.0 LANGUAGES C)
add_library(hello_library SHARED hello.c hello.def)
add_executable(hello_test hello.c)
set_target_properties(hello_library PROPERTIES
PUBLIC_HEADER hello.h
VERSION ${PROJECT_VERSION}
SOVERSION 1
OUTPUT_NAME "hello"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Hex_Identity_ID_Goes_Here"
)
2.2. 為了指示要將函數(testWork, add, reverse 等等) 導出來(export)使用, 要編輯一個文字檔案 hello.def:
LIBRARY hello
EXPORTS
testWork
add
reverse
2.3. 編輯一個 c 程式,用來宣告函式原型的標頭檔 hello.h:
void testWork( );
int add(int a, double b);
char *reverse(char *str);
2.4. 編輯 c 模組程式的原始碼 hello.c:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "hello.h"
int main( ) {
testWork( );
printf("3 + 5 = %d\n", add(3, 5));
printf("%s\n",reverse("Hello-String") );
return 0;
}
void testWork( ) {
printf("It works!\n");
}
int add(int a, double b) {
return a + b;
}
char *_bufferPointer = NULL;
int _buffrLength = 0;
char *reverse(char *src) {
int size = strlen(src);
if(size == 0) return NULL;
else if(size > _buffrLength) { // allocate if space not enough
if(_bufferPointer != NULL) free(_bufferPointer);
_bufferPointer = (char *)malloc(size + 1);//from heap
_buffrLength = size;// maximum characters to hold
}
_bufferPointer[size] = '\0';// eos
for (int i = 0; i < size; i++) _bufferPointer[size - 1 - i] = src[i];// reverse
return _bufferPointer;
}
2.5 上述檔案都編輯完成後就能執行 make 作業, 製作成動態程式庫 (hello/libhello.so)
cmake .
make
3. 在專案內新增 pubspec.yaml 檔案, 完成後, 開啟終端機執行 pub get 準備使用 ffi:
name: cinterop
version: 0.0.1
description: >-
An example of calling C code from Dart through FFI
author: MyName<email@google.com>
environment:
sdk: '>=2.6.0 <3.0.0'
dependencies:
ffi: ^0.1.3
dev_dependencies:
test: ^1.5.3
4.編輯一個透過 ffi 界面呼叫 c 語言的 dart 範例程式 main.dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
main( ) {
final libso = DynamicLibrary.open('hello/libhello.so');
final testFun= libso.lookup<NativeFunction<Void Function( )>>('testWork').
asFunction<void Function()>();
final addFun = libso.lookup<NativeFunction<Int32 Function(Int32, Double)>>('add').
asFunction<int Function(int,double)>( );
final revFun = libso.lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>('reverse').
asFunction<Pointer<Utf8> Function(Pointer<Utf8>)>( );
final c2dartString = (Pointer<Utf8> _pointer) => Utf8.fromUtf8(_pointer);
final dartstring2c = (String _string) => Utf8.toUtf8(_string);
final reverse = (String _string) => c2dartString(revFun(dartstring2c(_string)));
testFun( );
print('adder: 334 + 5 = ${addFun(334, 5.0)}');
for(int i=0; i<20 ; i++) print('${reverse("$i.Hello-string")}');
}
5. 用 dart VM 執行上述 main.dart 看看: /pathToDartSDK/bin/dart main.dart
It works!
adder: 334 + 5 = 339
gnirts-olleH.0
gnirts-olleH.1
gnirts-olleH.2
gnirts-olleH.3
gnirts-olleH.4
gnirts-olleH.5
gnirts-olleH.6
gnirts-olleH.7
gnirts-olleH.8
gnirts-olleH.9
gnirts-olleH.01
gnirts-olleH.11
gnirts-olleH.21
gnirts-olleH.31
gnirts-olleH.41
gnirts-olleH.51
gnirts-olleH.61
gnirts-olleH.71
gnirts-olleH.81
gnirts-olleH.91
後記: dart SDK 的 dart2native 可以將原始碼轉成可執行檔, 不用 dart VM, 直接就能執行:
/pathToDartSDK/bin/dart2native main.dart && ./main.exe
2020年2月7日 星期五
Javascript 讀取 blob
// arrayBuffer reader
async function blobReader(_blobArray) {
const blobTemp = new Blob(_blobArray);
if( blobTemp.arrayBuffer ) return await blobTemp.arrayBuffer( );
else { // use FileReader & Promise to read
var _ready = null;
const _arrayBuffer = new Promise((_resolv, _) => _ready=_resolv);
const _reader = new FileReader( );
_reader.onloadend = ( ) => _ready && _ready(_reader.result);
_reader.readAsArrayBuffer(blobTemp);
return await _arrayBuffer;
}
}
2020年2月5日 星期三
Javascript 使用 html5 的 MediaSource 播放器
<html><head><meta charset='utf-8'></head><body><script>
const codec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
class HttpPlayer {
constructor (width, height) {
const video = document.createElement('video');
const mediaSource = new MediaSource( );
mediaSource.onsourceopen = _evt => {
var completeFile = null;
var number = 1; // total number to get
const sourceBuffer = mediaSource.addSourceBuffer(codec);
const httpGet = _filename => {
const url = `https://${location.origin.split("//").pop( )}${_filename}`;
const https = new XMLHttpRequest( );
https.responseType = 'arraybuffer';
https.onload = ( ) => sourceBuffer.appendBuffer(https.response);
https.onloadend = ( ) => completeFile = _duration => {
completeFile = null;
number -- <= 0 ? mediaSource.endOfStream( ) : (( ) => {
sourceBuffer.timestampOffset = _duration - 2;
httpGet("/streamN.fmp4"); // next fmp4
})( );
}
https.open('get', url);
https.send( );
}
sourceBuffer.onupdateend = _evt =>
completeFile && completeFile(mediaSource.duration);
httpGet("/stream0.fmp4"); // initial fmp4
}
video.width = width;
video.height = height;
video.muted = true;
video.controls = "controls";
video.autoplay = "autoplay";
video.src = URL.createObjectURL(mediaSource);
document.body.append(video);
}
};
new HttpPlayer(1024, 768);
</script></body></html>
2019年12月25日 星期三
用 android ndk 編譯 bento4
bento4 官方網站原始碼: https://www.bento4.com/downloads/
android ndk官方網站: https://developer.android.com/ndk/downloads
到上述官網下載 bento4 原始碼, 及程式編譯工具 android ndk, 分別將它解壓縮. 例如我將它解壓縮, 另外再改名, 分別放在目錄:
export path_to_bento4=/home/mint/Downloads/bento4
export path_to_ndk=/home/mint/Downloads/ndk20
1. 稍微修改 bento4 原始碼的 Ap4Config.h, (位在 $path_to_bento4/Sourece/C++/core/Ap4Config.h), 找出並修改 AP4_fseek 及 AP4_ftell 的定義:
#define AP4_fseek fseek
#define AP4_ftell ftell
1. 確認自己下載的 ndk 版本(最少要 24), 現在用 29, 用來設定 ANDROID_PLATFORM 環境變數, 目的是讓 AP4_fseek 及 AP4_ftell 支援 64 位元的 ftello , fseeko, 若只使用 ftell 及 fseek 則開啟檔案只能支援 32 位元長度 2^32 = 4G
2. 在 linux shell 命令列模式要先設定好 ndk 的環境變數:
export ANDROID_NDK=$path_to_ndk
3. 切換到 betnto4 主目錄, 首先用 cmake 產生 CMakeLists.txt, 接著用 make 下指令(-j3 用 3 個 cpu 加速編譯速度), 讓 ndk 開始編譯 c 程式:
cd $path_to_bento4 && mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=29 ..
make -j3
4. 許多有用的工具程式都已編譯成 android 下可直接執行的 binary 檔, 放在目錄:
$path_to_bento4/build
像是 mp4Fragment 可以將 mp4 轉換為 fmp4. fmp4 格式檔是一個可以透過 javascript 餵給 html5 MediaSource 播放的片段串流影音檔:
mp4Fragment path_to_source.mp4 path_to_taget.fmp4
5.在 Android app 透過一些技巧, 例如將上述編譯完的執行檔包在 assets, 執行 app 時再將它解開,放進 app cache 目錄內. 透過 ProcessBuilder 修改成可執行權限, 就像在終端機執行一樣, http 伺服器再將轉成的 fmp4 透過網路傳給客戶端, 就成了名符其實的媒體串流伺服器 app.
用 python 解簡單的常微分方程式
# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...
-
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 就字面意義來說 Solder Mask (防焊層)就是要把焊接(電氣)層遮掉, Paste Mask 則是要把護膠層遮掉,兩者使用時機並不同, 前者用在 PCB 製程, 後者用於主機板組立製程(PCBA)開鋼板. 2. 在 PCB 的製程中,一塊銅箔基板,為了要線路化,...