2021年4月22日 星期四

[PHP] 下載 Maxmind GeoIP Legacy Databases 和 ngx_http_geoip_module 相關處理 @ macOS 11.2, PHP 7.3.24

由於 Maxmind 在推 GeoIP2 ,不開放 .dat 的 GeoIP DB 了,以前下載位置:

  • http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
  • http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
現在官方都說要改用 GeoLite2 DB ,其格式:
  • GeoIP2 Binary (.mmdb)
  • GeoIP2 CSV 
目前先偷懶不處理 nginx ,因為 nginx 採用 GeoIP Legacy Databases。

有找到一個網站 https://www.miyuru.lk/geoiplegacy,從中下載:
連續動作:

% php -v
WARNING: PHP is not recommended
PHP is included in macOS for compatibility with legacy software.
Future versions of macOS will not include PHP.
PHP 7.3.24-(to be removed in future macOS) (cli) (built: Dec 21 2020 21:33:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.24, Copyright (c) 1998-2018 Zend Technologies

% php composer.phar require geoip/geoip:~1.16

% wget https://dl.miyuru.lk/geoip/maxmind/city/maxmind4.dat.gz
% gunzip -d maxmind4.dat.gz

% cat test-ip-via-geoip.php
<?php
require 'vendor/autoload.php';
$gi = geoip_open("maxmind4.dat",GEOIP_STANDARD);
$country = geoip_country_code_by_addr($gi, $argv[1]);
echo "lookup [".$argv[1]."], result: [$country]\n";

% php test-ip-via-geoip.php 8.8.8.8
lookup [8.8.8.8], result: [US]

% nslookup tw.yahoo.com
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
tw.yahoo.com canonical name = atsv2-fp-shed.wg1.b.yahoo.com.
Name: atsv2-fp-shed.wg1.b.yahoo.com
Address: 180.222.102.201
Name: atsv2-fp-shed.wg1.b.yahoo.com
Address: 180.222.102.202

% php test-ip-via-geoip.php 180.222.102.202
lookup [180.222.102.202], result: [IN]

% curl ipinfo.io/180.222.102.202
{
  "ip": "180.222.102.202",
  "hostname": "media-router-fp74.prod.media.vip.tp2.yahoo.com",
  "city": "Taoyuan City",
  "region": "Taiwan",
  "country": "TW",
  "loc": "24.9937,121.2970",
  "org": "AS24506 YAHOO! TAIWAN",
  "timezone": "Asia/Taipei",
  "readme": "https://ipinfo.io/missingauth"
}

% nslookup facebook.com
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
Name: facebook.com
Address: 31.13.87.36

% curl ipinfo.io/31.13.87.36
{
  "ip": "31.13.87.36",
  "hostname": "edge-star-mini-shv-01-tpe1.facebook.com",
  "city": "Hong Kong",
  "region": "Central and Western",
  "country": "HK",
  "loc": "22.2783,114.1747",
  "org": "AS32934 Facebook, Inc.",
  "timezone": "Asia/Hong_Kong",
  "readme": "https://ipinfo.io/missingauth"
}

% nslookup www.gov.tw
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
Name: www.gov.tw
Address: 223.200.155.55

% php test-ip-via-geoip.php 223.200.155.55
lookup [223.200.155.55], result: [TW]

% curl ipinfo.io/223.200.155.55
{
  "ip": "223.200.155.55",
  "hostname": "223-200-155-55.hinet-ip.hinet.net",
  "city": "Hualien City",
  "region": "Taiwan",
  "country": "TW",
  "loc": "23.9769,121.6044",
  "org": "AS4782 Data Communication Business Group",
  "timezone": "Asia/Taipei",
  "readme": "https://ipinfo.io/missingauth"
}

2021年4月9日 星期五

[PHP] 透過 Maxmind GeoIP DB 統計用戶資訊 @ macOS 11.2, PHP 7.3.24

真是超級久沒用 maxmind.com 的 GeoIP DB 了,一時之間還以為沒有免費使用的方式,追了一下是要註冊帳號才能下載,而 maxmind 也有提供自動化每天更新 GeoIP DB 的機制。

在此對一份類似 Access Logs 做分析,將其轉成 CSV 格式來分析,其中 CSV 裡頭有 id 跟 remote_ip 兩個欄位,將 remote_ip 分析完後直接歸類屬於哪個 country_code,程式碼很簡單:

% php -v

WARNING: PHP is not recommended
PHP is included in macOS for compatibility with legacy software.
Future versions of macOS will not include PHP.
PHP 7.3.24-(to be removed in future macOS) (cli) (built: Dec 21 2020 21:33:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.24, Copyright (c) 1998-2018 Zend Technologies

% cat composer.json
{
  "require": {
    "geoip2/geoip2": "~2.0"
  }
}

% php composer.phar install
No lock file found. Updating dependencies instead of installing from lock file. Use composer update over composer install if you do not have a lock file.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 4 installs, 0 updates, 0 removals
- Locking composer/ca-bundle (1.2.9)
- Locking geoip2/geoip2 (v2.11.0)
- Locking maxmind-db/reader (v1.10.0)
- Locking maxmind/web-service-common (v0.8.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
- Installing composer/ca-bundle (1.2.9): Extracting archive
- Installing maxmind/web-service-common (v0.8.1): Extracting archive
- Installing maxmind-db/reader (v1.10.0): Extracting archive
- Installing geoip2/geoip2 (v2.11.0): Extracting archive
2 package suggestions were added by new dependencies, use `composer suggest` to see details.

Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!

% cat job.php
<?php

require 'vendor/autoload.php';

// https://github.com/maxmind/GeoIP2-php
use GeoIp2\Database\Reader;
$reader = new Reader('GeoLite2-City.mmdb');

// https://www.php.net/manual/en/function.fgetcsv.php
$row = 1;
$header_row = NULL;
$header = array();
$country_group = array(); 
$lookup = array();

$tracking_time_cost_per_run = microtime(true);
echo "[".date("Y-m-d H:i:s")."]\tinit\n";
if (($handle = fopen("access_log.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 10240, ",")) !== FALSE) {
if (is_null($header_row)) {
$header_row = $data;
foreach($header_row as $index => $name) {
$header[$name] = $index;
}
continue;
}
$remote_ip = $data[ $header['remote_ip'] ];
$data_id = $data[ $header['id'] ];

try {
$record = $reader->city($remote_ip);
} catch (Exception $e) {
continue;
}
if (!is_null($record) && property_exists($record, 'country') && !is_null($record->country) && isset($record->country->isoCode)) {
if (!isset($country_group[$record->country->isoCode]))
$country_group[$record->country->isoCode] = array();
$hash_key = $remote_ip.'-'.$record->country->isoCode;
if (!isset($lookup[$hash_key])) {
$lookup[$hash_key] = 1;
array_push($country_group[$record->country->isoCode], $data_id);
}
}
++$row;
if ($row % 100000 == 0) {
echo "[".date("Y-m-d H:i:s")."]\t".number_format($row).", time cost: ".(microtime(true) - $tracking_time_cost_per_run)."\n";
$tracking_time_cost_per_run = microtime(true);
file_put_contents('/tmp/lookup-result.json', json_encode($country_group));
}
}
fclose($handle);

echo "[".date("Y-m-d H:i:s")."]\t".number_format($row)."\n";
echo "Total #: $row\n";
print_r($header);
file_put_contents('lookup-result.json', json_encode($country_group));
}

產出:
% time php job.php
[2021-04-09 21:03:51] init
[2021-04-09 21:05:18] 100,000, time cost: 86.867056131363
[2021-04-09 21:06:41] 200,000, time cost: 83.395349025726 
...

% jq '' /tmp/lookup-result.json | head -n10

{
  "TW": [
    "1",
    "68",
    "101",
    "121",
    "147",
    "193",
    "236",
    "259", 
... 

就把一堆 record id 擺在某個 country_code 下面,每 85秒處理完 10萬筆資料。

2021年3月14日 星期日

[動漫] 排球少年!!

 

 來源:WIKI

前幾天晚上想測試一下愛奇藝 CDN 不在台灣後,影響有多大時(實際上愛奇藝沒了台灣這邊的CDN後,速度真的受到不小影響,一直緩衝,幾乎不可用)當下就交叉比對了 NETFLIX ,就挑了 排球少年 這動畫。共有四季,就這樣一週多一點,都用 1.5倍速 完食!

老實說,我並沒有很喜歡這畫風,論運動的話,大家更愛看帥哥眾多的網球少年。會一直看下去的主因是自己國高中也打了六年排球,在班際排球賽上,我也常常負責舉球手這個位置,原因沒有別的,因為排球風氣還不盛行,湊起六人後,隊友大多都是打籃球的,跳得高但基本動作較不熟,就非常適合當主攻手,由相對球齡長的我,角色就是幫忙托球給予攻擊機會。當初國一會選擇排球也滿有趣的,因為國小自己點太多技能在躲避球,幾乎沒在打籃球,一進入國中後對大家一窩蜂衝籃球沒有太大興趣,就挑了排球社。還記得第一天社團活動時,跑去遠一點的撿球,就...故意高吊遠傳兩三個排球場距離,球卻不偏不倚的砸中排球社高中學姊,不認識的學姊還很好心的騙我說眼鏡是自己壞的 XD 當下很蠢以為真的是自己壞,事後想起真是人太好!(後來...好像被詛咒一樣,每當自己把球往高空拋超高時,很容易打中別人的頭 Orz 高中時期又一次打到自己的高中好友)

記得自己國高中垂直跳60cm左右,周邊一票人都 80cm 或是身高 175cm 以上,最扯的是有一位 170cm 卻可以垂直跳 94cm 的學長,所以,對於排球少年的故事很有共鳴的!舉凡跳發、飄浮球都打過,我自己應該是主要用飄浮球發球的。甚至大一體育課也可以靠發球吃香得個幾分 XD

這動畫對我來說,滿多球員內心戲,例如為何打排球、為何不想努力衝刺一番、為何不想認輸、為何想偷懶等等的,滿好玩的,有些心境跟當年自己玩排球有像,甚至上了大學後,短暫加入系隊幾個月有感。像是看到別人身體條件很優,就開始想偷懶,自己找理由畫地自限。

真的想起了很多青春歲月,雖然沒有像故事裡不斷去追逐競賽,但打排球的時間,卻填滿了我國高中至少三四年眾多的下課活動,幾乎每天下課等車前,都會玩個30分以上。可惜上大學後,心態越來越疲憊?無動力,就真的很快退出系隊,幾乎沒在打了。

最後,對於這齣動畫共鳴滿滿,但也難抵擋後面稍微膩的慢步調 XD 像是第三季 10 集動畫就只有一場比賽,卻佔了一季啊。有機會再來看看漫畫了,有興趣想看看排球少年動畫的,可以在 愛奇藝瘋動畫(僅第四季)NETFLIX 逛逛囉!

2021年3月4日 星期四

[開箱] EZCast Beam V3 ,順便與 EZCast Beam J2 體積比較

 EZCast Beam V3

最近想找個伴手禮送人,想著想著就想起投影機很適合給家裡有小孩的人。挑了偏中低價位的投影機,至少有 720P 的輸出效果。剛好手中也有台 EZCast Beam J2 就拿來比較一下大小。

EZCast Beam V3

EZCast Beam V3

比完才發現 EZCast Beam J2 真的是袖珍版的微投影機 XD

這台 EZCast Beam V3 的服務項目:

  • HDMI 輸入 (投影機最基本需求)
  • USB 輸入
    • 支援 iPhone/iPad 透過有線完成 Airplay Mirror 投放
    • 支援 Android 透過有線完成 Chromecast Mirror 
    • 支援從隨身碟讀取資料來播放,如影片/照片/音樂
  • 無線輸入
    • 由於支援2.4G/5G 無線網路連線,有無線投放功能,如 DLNA / Miracast / Google Home Mirror / Airplay ,甚至官網還有提到 Youtube app 也可以搜尋到此裝置來投放
送人著重在體驗,力求極速上手,個人比較看重 USB 輸入,讓手機內容投放出去,方便第一!

更多資訊可以到官網 EZCast Beam V3 或是 EZCast網站購物 或是官方直營的 EZCast蝦皮商店 逛逛。

2021年3月1日 星期一

初探保險 - MY83保險網 / Finfo保險資訊站

來源:FINFO.TW 30歲罐頭保單的年費變化

差不多該研究保險了,之前一直偷懶是認為健保已非常夠用了!然而,對於家庭角色來說,保險有其特別的意義,如果有玩過遊戲 太空戰士七 的話,其寶石搭配的設計非常豐富,其中有一招是被敵人KO時,招喚不死鳥讓全體成員復活的機制,保險就是類似這種效果,核心設計是降低身旁親人的壓力,在經濟許可上,播一些擺在保險上當作理財規劃的一環作為保護。

這次研究保險時,主要透過了 MY83保險網FINFO保險資訊站 ,兩者的罐頭保單體驗都在伯仲之間,後來就留言跟專業的保險員互動,果真,很多事還是交給專業的來吧!保險員他們有的優勢:
  • 考過試
  • 比一般人熟悉保單設計、優惠和組合搭配
  • 有些專屬保險產品不是一般人可以買到的,屬於獨家商品
其實,這就跟開證券戶一樣,在網路可以痛快的申請完畢,但取得的交易手續費是公定價。如果透過證券營業員幫忙開戶,就可以拿到優惠的交易手續費。而透過保險員,有一些保險產品是獨家販售的。

我認為有興趣的可以大膽一點留訊息,實在是這些網站為了給網站使用者有更佳的服務體驗,有做了以下事務:
  • 網站平台透過考試/面試挑選配合的保險業務員
  • 每個業務員是被網站平台分配到客戶的
  • 網站平台還會時時數據追蹤,以確認保險業務員成交的轉換率為何
  • 保險業務員本身需要這類網站來取得來源
透過上述架構,有機會被分配到不錯的保險業務員,且業務員為了能夠有成交/業績/永續配分配到客戶,服務品質不會太差的。當然,也要花點心思去看對方配置的保險方案,去理解保險員配置思維。例如有次保單保險員配了兩個台灣人壽的主約,仔細去看才發現配置兩個主約的目的是讓其中一個附約的保障金額可以拉高,有些附約的保障金額限制不能超過主約保障金額的五倍價格,而通常主約都保得很低,靠附約搭配提升不少值。這時拆分多個主約就有其設計架構。

這些便是我這次接觸保險的心得,套句以前有當過保險業務的長輩分享,可以著重意外跟失能即可,而實支實付也可以考慮,但投資理財保單則不用花太多心思,因為投資類的方式有太多了,像是自我投資也是一種。

最後一提,失能可以分成意外失能跟疾病失能,後者的保費貴跟條件嚴謹,像是身體狀況有定期回診的,通常就無法用一般合理的保險額度來保疾病失能,但也存在非常昂貴的疾病失能險或是不保證能年年續保的失能險可挑選的。有興趣可以多多利用 FINFO保險資訊站 服務,可以觀看年紀與保費變化走勢囉。

2021年2月21日 星期日

[動畫] 弱角友崎同學 , 輕小說

農曆年節開始玩起了 DOS GAME 大富翁三 ,溫習了一下 XD 有了社會歷練後,玩起大富翁遊戲時,停留在銀行時,立馬辦貸款、辦信用卡並借好借滿(遊戲內是幾乎無風險的操作),經過股市立刻買好買滿,隨著戰況激烈,物價指數攀升,當然,股價也有對應的成長的。這大概就是長大後才懂的事,小時玩這遊戲還沒這種企圖心。

接著就回憶起在 bilibili 看到的 動畫 弱角友崎同學 這齣輕小說 動漫化的作品,提醒自己要好好進攻現實生活 XD 

這故事讓人想起自己國中時期,國一時段考都考個四百多名吧,全校 670 人,也就是全班 60人中,大概 35名之後。成天也在玩 PC Game ,倒也想不起自己有沒認真唸書過。但是,進入國二後,很神奇地在 物理 得到了解題興趣,彷彿不用花太多時間,只需理解又不用花時間背,極爽 XD 就這樣因為單一科物理的學習優勢,讓我整體學習心態改變,開始會用力花時間也把歷史和地理也背好,如此讓政體成績進入到全校排名 100 內。

這些過程,就像小說/漫畫 弱角友崎同學 的情節有那麼一點點近似,在人生中找到一點回饋感,進而把握去攻略人生。一樣的思維在十多年前也可以用在寫 Blog、研究技術等等,要再來找找生活回饋感了!

2021年2月18日 星期四

[開箱] ADONIT NOTE+ 觸控筆 與 iPad 8 ,使用 SketchBook app 測試傾斜效果

之前買了像 小米液晶手寫板 給小孩塗鴉,非常開心。最近在想怎樣引導小孩產生高質量的塗鴉,想著想著手邊就蹦出一台 iPad 8 跟第三方觸控筆 ADONIT NOTE+ 了!!說真的太久沒買 iPad ,第一次買是2011年的 iPad2 (代號 A1395) ,第二次買是 2013 年的 iPad Air (代號 A1474),最近才發現 iPad 第七代已經來到 10.2 吋。而添購 iPad 8 的主因之一是追求觸控筆支援,以及福利機價差不多,就乾脆買新的了!

試用了一下還滿順的,可惜的 2013 年 iPad Air 如預期的完全無法用,不然我也想添購一隻筆來輔助一下 XD 像是變成另一個畫板等等

2021年2月9日 星期二

Clubhouse 體驗心得


大概六天前剛拿到朋友的邀請,上去體驗了一下,非常新鮮。

聽到了海外大陸人在跟台灣人聊政治,且是很有理性的論述民主的好壞,像是台灣人說民主也沒因此比較多選擇(藍綠兩黨),還是會被媒體給包裝起來,但是!若選錯市長時,民主至少可以罷免!對於過去不認同的政治,可以身體力行去表達自己的心聲。而常說的民主會拖累經濟的議題,恰好有位在德國的大陸人在分享北上廣深的 GDP 其實跟台灣差不多,再加上自己老鄉連廁所都沒有,以此驗證民主也不會拖累經濟,講得還滿不錯的。

聽了在紐約的大陸人,在聊在美國或 App 朋友交際認識,不如線上遊戲那般親,線上遊線連討論個攻略,大家都毛起來教學 XD 而其他則是提到自己遠離曼哈頓,特不愛曼哈頓的急促、到時都要記得去抗爭(類似被欺負要去哪邊回報?!),聊仍在疫情中,跟大家介紹 crowdcow.com 可以買好吃又便宜的牛肉 XD 以及自己住的地方偏郊區,卻有什麼類似快捷大眾運輸,快速到市中心以及還可以轉乘到機場,且交通費便宜啊。是個很讚的聊天內容。

聽了一位台灣女生到北京外商 SaaS 發展,之前看了幾篇質量還不錯的科普文章,沒想到一年多沒更新是到北京發展了!聊了不少在 SaaS 的步調,包括公司估值方式或為何 SaaS 總是高估值、聊了訂閱制的經營方式,會有客戶經理在負責維護客戶忠誠度等等,也滿有意思的。途中也吸引到有一位從事投資的,回饋他在業界中常見的估值法,如同業比較法、評估新創的事業成長率等等,這段就還滿像 Youtuber 財經網紅 CK 的部分,就 CK 是一名在澳洲精算師,前陣子除了分享對總體經濟外,也分享她是怎樣評估股價(市值),過程就包括猜測成長率、套用估值公式等等,因此聽負責投資其他間公司的VC說話,特別有共鳴。

聽了葉丙成教授在那邊台語五四三,也滿逗趣的。不知哪個時間點自己也會變成那樣,喜歡跟幾位老友聊是非。讓我想起以前在工研院的老闆,英文很強,但總是愛台語,還會請特休去參加抗議活動,實在熱血 XD

聽了財報狗產品經理在分享他的工作方式,包括大老闆很挺他,也讓他去經營數個月的 podcast ,最終因為數據上並沒有顯著帶給財報狗對應的流量,再加上一集至少要五小時處理,每週一集的時間消耗也是很恐怖的。比較特別的是財報狗 PM 提到了一切都是數據導向,就算花兩週開發服務,最後的數據是差的,仍是會捨棄不上線的!而在招聘 RD 時,從一開始就會溝通可能高達 2/3 的 coding 都不會上線!大量專注在實驗、數據、決策等 growth hack 路線,對我而言是滿強化一些已知的手段,非常醒腦。而聊天提到了一些粗略的表單,沒想增加設計美感後,出現轉換率變差時,該怎樣去檢驗一直猜想,若拉不起來也不會上線的!核心 KPI 就是轉換率,轉換率沒變好就不會採用。非常專注。

也聽了一些早晨的新聞台,這我反而記憶最淺,而快速體驗後,感受到網路上的財經網紅會盡可能想搶流量,也不少在該領域非第一的網紅很努力地連續幾天都在開講,我一想到上班日都在做工作的事情,週末還要跑去聽工作相關的,而聊天品質又無法確定,實在累。

我想,嘗試過 clubhouse 後,對於一些尚未深入到 podcast 用戶,應當會感受非常新鮮。但對於已熟悉 podcast 來源者,可能會像我一樣又回味起 podcast 的高品質體驗。包括 podcast 可以快轉、可以看別人的評價,而 clubhouse 就是直播,且很有機會快速跟名人親近,是一種老早就存在的需求,但又重新被詮釋出來。然而 clubhouse 滿殺時間的,若參加到一場不錯的聊天,那心中會很充實,但如果加入到一場主持人不佳,講者也佳,想詮釋自己的想法也說不清時,那真的會聽不下去的 XD

果真核心項目是有限的時間資源,大家都在搶,搶人眼球、搶人耳朵、搶人時間,早已進入到任何的新創服務,就是要硬生生地佔領用戶的時間資源。而我,應該還是會回歸到 podcast 服務上,像是對岸的 得到app 也滿不錯的。這種專業的聲音產出內容,有那麼一種可預期的體驗感,有那麼可以快轉省時間的沾沾自喜感,而資訊/知識,其實也不打折的。

2021年2月3日 星期三

Flutter 開發文件之 iOS 與 Android 實作筆記 - 使用 Admob 以 Interstitial ads 為例


關於 flutter 使用 Admob 的架構,可參考網路上數篇文章:
此例筆記 iOS & Android 的設定方式,並且記錄碰到的錯誤訊息。

Android app 設定方式:

1. 更新 flutter_app/pubspec.yaml 添加 dependencies (記得要運行 flutter pub get)

firebase: ^8.0.0
firebase_admob: ^0.11.0+1

2. 安置從 Firebase 建立專案時得到的 google-services.json 檔案,擺在 flutter_app/android/app/ 中

3. 更新 flutter_app/android/build.gradle 的 buildscript -> dependencies 設定

//classpath 'com.android.tools.build:gradle:3.5.0'
// 為了修正錯誤訊息:error: unexpected element <queries> found in <manifest>.
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.3'

4. 更新 flutter_app/android/gradle/wrapper/gradle-wrapper.properties 的 gradle 版本至 6.1.1

#為了修正錯誤訊息:Minimum supported Gradle version is 6.1.1. Current version is 5.6.2. If using the gradle wrapper
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

5. 增加 flutter_app/app/build.gradle 的 apply 項目

apply plugin: 'com.google.gms.google-services'

6. 更新 flutter_app/android/app/src/main/AndroidManifest.xml 添加 meta data (僅範例程式數值)

<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/>

7. 更新 flutter_app/lib/main.dart (此改自於範例程式,讓 add Button 可以產生一個插頁廣告)

iOS app 設定方式:

1. 更新 flutter_app/pubspec.yaml 添加 dependencies(記得要運行 flutter pub get)

firebase: ^8.0.0
firebase_admob: ^0.11.0+1

2. 安置從 Firebase 建立專案時得到的 GoogleService-Info.plist ,用 Xcode 打開專案,把 GoogleService-Info.plist 拖拉擺入跟 Info.plist 的同階目錄位置。

% open flutter_app/ios/Runner.xcworkspace
% ls flutter_app/ios/Runner/GoogleService-Info.plist

3. 更新 Info.plist 內容,添加資訊 GADApplicationIdentifier 等資訊(僅範例程式數值)

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
<key>SKAdNetworkItems</key>
  <array>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>cstr6suwn9.skadnetwork</string>
    </dict>
  </array>

4. 更新 Podfile ,上方添加 platform :ios, '10.0'

為了修正錯誤訊息: 
Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was specified. Please specify a platform for this target in your Podfile.

5. 更新 flutter_app/lib/main.dart (此改自於範例程式,讓 add Button 可以產生一個插頁廣告)

如此就收工啦!而 flutter_app/lib/main.dart 內容如下:

import 'package:flutter/material.dart';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_admob/firebase_admob.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
    keywords: <String>['flutterio', 'beautiful apps'],
    contentUrl: 'https://flutter.io',
    childDirected: true,
    nonPersonalizedAds: true,
  );

  InterstitialAd _interstitialAd;

  InterstitialAd createInterstitialAd() {
    return InterstitialAd(
      adUnitId: InterstitialAd.testAdUnitId,
      targetingInfo: targetingInfo,
      listener: (MobileAdEvent event) {
        print("InterstitialAd event $event");
      },
    );
  }

  @override
  void initState() {
    super.initState();
    FirebaseAdMob.instance.initialize(appId: FirebaseAdMob.testAppId);
    //_interstitialAd = createInterstitialAd()..load();
  }

  @override
  void dispose() {
    _interstitialAd?.dispose();
    super.dispose();
  }

  void _incrementCounter() {
    _interstitialAd?.dispose();
    _interstitialAd = createInterstitialAd()..load()..show();
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

2021年1月23日 星期六

Chromecast with Google TV - 透過網頁安裝 愛奇藝 TV app 與 愛奇藝手機程式投放到 Chromecast


前陣子不小心買了 Chromecast with Google TV ,想說來試試看,結果裡頭一堆台灣內容提供者 App 不能安裝,如:

  • 動畫瘋
  • 愛奇藝
  • CATCHPLAY+
就明明在 應用程式 -> 娛樂 都有顯示一堆台灣的內容服務,卻說因為國家地區而無法安裝 Orz 推論可能是手上這台是美國貨...有地區限制吧?真是殘念

Updated @ 2021-02-03:

主要是我使用的 Google account 的地區落在美國。可以在 https://play.google.com/store/paymentmethods 的付款方式,查看網頁底部的 地 區資訊 

後來在找了一下,嘿嘿,原來可以靠網頁來安裝程式,立即測試了一下!但不是每一款都能安裝。

在 2021/01/23 成功者:
在 2021/01/23 失敗者:
後續再等看看會不會有什麼變化。

另外,愛奇藝其實有分新加坡(香港)跟北京兩個 App ,其中新加坡(香港)的手機程式可以直接投放到 Chromecast 上,而北京的不行,有興趣可以去找找!

2021年1月22日 星期五

Flutter 開發文件之 iOS 與 Android 實作筆記 - 使用 Firebase 回報 App 使用人數


先說一下,有出類似文件教人怎樣使用 Firebase - Admob 部分,在此僅流水帳紀錄引用 firebase 的用法,體驗一下透過 Flutter 同時開發 ios 和 android app 時,並且使用額外函式庫的過程。

原理:

1. 使用 Android Studio + Flutter plugin 來建立 Flutter 專案
- 在此 package name 使用 org.changyy.study.flutter
- 自動產生 android Package Name = org.changyy.study.flutter_app
- 自動產生 iOS Bundle ID = org.changyy.study.flutterApp
- 假定專案位於 ~/AndroidStudioProjects/flutter_app

2. 建立 Firebase 專案
- 在此使用 org-changyy-study-flutter 作為 firebase project name

3. iOS app
- 使用 Xcode 置入 firebase 的設定檔案 GoogleService-Info.list
- 用指令打開專案 % open ~/AndroidStudioProjects/flutter_app/ios/Runner.xcworkspace/
- 使用 CocoaPods 添加 Firebase 函式庫
- 在 AppDelegate.swift 置入 Firebase 啟動程式碼

4. Android app
- 依照 Firebase 專案建置過程安置 google-service.json 檔案
- 在 <專案>/build.gradle 設置相依套件 com.google.gms:google-service:4.3.4
- 在 <專案>/app/build.gradle 添加 plugin 和引入 com.google.firebase:firebase-analytics

5. 回到 Flutter 專案,分別叫出 iOS simulator 跟 Android emulator 運行


若沒意外,就可以同時在這兩個模擬器上跑出來,並且在 Firebase 專案後臺,看到兩個使用者:



2021年1月14日 星期四

[Javascript] 複寫 XMLHttpRequest 來紀錄 Network Request URL @ Chrome Browser DevTools

最近 podcast 很夯,來研究 <audio> 。之前研究過 <video> 就有發現現在都用很夯 blob 的播放方式,透過 JS 片段下載資料交給 <video> 播放,以至於從 JS 調閱出 <video> 元件時,看不到真實的影片來源,而是一連串 blob (Binary Large Object) 記憶體位置。

原本想說在 Chrome DevTools 下,能不能靠 JS 取得 network request 發送清單來做應用,看著看著突然腦筋一轉,乾脆就用 XMLHttpRequest 好了,多包一層就可以收集了。

用法:

//
// https://stackoverflow.com/questions/7775767/javascript-overriding-xmlhttprequest-open
//
(function() {
var proxied = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function() {
// console.log( arguments );
//
// 只關注 m3u8 來源
//
if (arguments.length >= 2 && arguments[1].indexOf('.m3u8') > 0) {
console.log( arguments );
// return;
}
return proxied.apply(this, [].slice.call(arguments));
};
})();

2021年1月9日 星期六

[開箱] Chromecast with Google TV - 2020/09/30 發表

Chromecast with Google TV

沒想到一進入 2021年,突然又手癢了!立馬買了 Chromecast with Google TV 來把玩。之前沒那麼想入手是因為台灣還買不到,以及自己已經有 Chromecast Ultra,興趣缺缺,但人果真很善變,過了幾個月就突然想起,又快速下手買了一個 XD 這次嘗試粉色系,連內附的電池外裝也粉色!

Chromecast with Google TV

這次體驗的心情,就像 Apple TV 的感受一樣,有個遙控器,並且跟市面上的新電視或電視和一樣支援語音輸入(語音搜尋),我之前對 Apple TV / Android TV 沒這麼愛的主因是輸入難用並認為手機就是本體,凡事都走投影來釋放手機資源,但對 Google TV 可以安裝 App 仍感興趣,這次測試了一下 NETFLIX 後,感覺 NETFLIX 的 UX 體驗在各平台皆非常相近,當下也不會覺得不方便(感興趣都在首頁,滑一滑點一下就可以觀看)

之前有朋友很想要觀看 NETFLIX 4K 內容,查了一下 Chromecast with Google TV 是有支援的,若真的可以用 49.99 美金買到,真的物超所值。這在 NETFLIX 官方文件也有提到:

以下 Chromecast 型號目前支援 Netflix 超高畫質串流功能:

Chromecast Ultra

Chromecast with Google TV

ref: 如何在 Chromecast 上使用 Netflix 

回過頭來,聊聊裝置的啟用,就包括會經過 Google Home app 掃描電視上的 QRCode,而手機過程也可以看到能多訂閱很多項目、音控辨識是否個人化等等,其中有個過程是會配對遙控器紅外線跟電視聲音的控制,十分貼心,因為許多類似設備就算有遙控器也沒規劃用紅外線去控制電視的聲音。

Chromecast with Google TV

感受比較深的是多了很多內容,以前用投影的方式必須自己去尋找有哪些內容,而採用 Google TV 則是大家為主動擠到這個 Android TV Google play 上,像是美國已上線的 Disney+ 等等,有趣的,我反而在 Android TV Google play 上,無法下載 myVideo, LiTV 這類本土程式,不知道是不是 Chromecast with Google TV 內有地區資訊,若是這種綁定關係,在意的就只能再等等吧?不知是不是本土商只在 TW region 上架,還是哪邊可以更改 Chromecast with Google TV 設備的所在地:

Chromecast with Google TV

Updated @ 2021/01/23:使用 Google Play 網頁版安裝愛奇藝

Updated @ 2021/02/03:主要是我使用的 Google account 的地區落在美國。可以在 https://play.google.com/store/paymentmethods 的付款方式,查看網頁底部的 地區 資訊

其餘細節是這款要求外部電源供應,直接插電視 USB 會提醒這是行不通的:

Chromecast with Google TV

最後,Google TV 上面的預設首頁也會隨著使用過的內容 App 節錄出該 App 的熱門內容,效果跟 NETFLIX 首頁很像,這樣一眼可以得知哪些感興趣可以點擊:

Chromecast with Google TV

相關文章:

2021年1月8日 星期五

[Linux] 使用 FlyVPN 與 EZCast EZC-5200 Hotspot 設置跳板開發服務(RTL8821CU Linux Driver) @ Ubuntu 20.04 Desktop

ezcast ezc-5200 ic

最近要模擬俄羅斯客戶使用產品上碰到的問題,由於產品是需要聯網的,手頭上的 AWS/GCP 都沒有俄羅斯節點,只好透過 FlyVPN 翻牆到莫斯科,而 FlyVPN 操作方式是非常簡單的,並且提供了各類平台的運行程式。

若服務邏輯可以在 PC 上執行,那就很搭配 FlyVPN 快驗證完,但如果是要在聯網裝置驗證,下一刻就得研究如何讓設備也處於莫斯科環境了!想了想,就是拿一台 Ubuntu Desktop 建立 Hostspot 環境,再讓 IoT 等設備透過 Hotspot 聯外就對了。

整個過程就是先從 EZCast 官網左下角的產品型錄(Product Catalog),得知 EZCast EZC-5200 是採用瑞昱的 RTL8821CU 晶片,接著研究一下 RTL8821CU Driver,就屬 brektrou/rtl8821CU 維護這份最不錯(很奇怪為何不在 RealTek 的官網找到 XD 或是類似官方 github.com/rtlwifi-linux 找到),且各處的程式碼編譯上都可能有問題,例如 linux kernel 大於 5.8 會踩到一些重複    定義的 structure 問題等等,總之,這次在 github.com/brektrou/rtl8821CU 得到很好的編譯體驗。

而挑選 Ubuntu Desktop 是比較適合打包成 VM ,方便傳遞給需要的同事當作測試環境,類似的做法在 Windows 也是能對應實現的(在 Windows 跑 FlyVPN 、建置熱點等等)。在此使用 VirutalBox 並且把 USB 設定自動掛在這 EZC-5200 網卡

virtualbox-usb-device-auto-mount

流水帳 - 環境建置:

$ sudo apt update 

$ sudo apt upgrade

$ sudo apt autoremove

$ sudo apt autoclean

$ sudo apt install net-tools git curl wget tmux vim dkms build-essential bc unzip linux-headers-$(uname -r) -y

流水帳 - 編譯 Linux Driver:

$ git clone https://github.com/brektrou/rtl8821CU.git

$ cd rtl8821CU

$ sudo ./dkms-install.sh

$ sudo usb_modeswitch -KW -v 0bda -p 1a2b

$ sudo systemctl start bluetooth.service

$ sudo vim /lib/udev/rules.d/40-usb_modeswitch.rules

在 LABEL="modeswitch_rules_end" 之前添加

# Realtek 8211CU Wifi AC USB

ATTR{idVendor}=="0bda", ATTR{idProduct}=="1a2b", RUN+="/usr/sbin/usb_modeswitch -K -v 0bda -p 1a2b"

$ sudo reboot

接著進入系統後,從右上角就可以看到無線網路了,進入無線網路的設定,想要開啟熱點時,會收到 "系統原則禁止做為熱點" ,查了一下貌似 Ubuntu 20.04 的 bug ?主因是權限不夠大,但這段應當要有用 sudo 來運行的方式才對?總之,就改用 root 登入即可,而 GUI 預設是不允許 Root 登入的。

ubuntu-20.04-wifi-hotspot-error

流水帳 - 啟用 Root 登入 GUI 方式:

先替 Root 設定密碼:

$ sudo passwd

開放 Root 登入權限:

$ sudo vim /etc/gdm3/custom.conf

添加

AllowRoot = true

$ sudo /etc/pam.d/gdm-password

添加 # 註解下段限制

# auth   required        pam_succeed_if.so user != root quiet_success

$ sudo reboot

最後在 GUI 登入介面上,挑選其他帳號(點擊"沒有列出來?"),就可以靠輸入帳號密碼的方式,用 root 登入,登入後就可以順利建制 Hotspot 出來使用

流水帳 - 查看是否有偵測到 USB WIFI:

$ lsusb

Bus 001 Device 003: ID 0bda:XXXX Realtek Semiconductor Corp. 802.11ac NIC

...

Ubuntu 20.04 Hotspot 建立方式:

右上角網路Icon -> 無線網路未連線 -> Wi-Fi 設定值 -> 新視窗的右上角 ... -> 開啟 Wi-Fi 熱點 -> 設定 SSID 跟 PSK 就可以完成啟動

ubuntu-20.04-wifi-setup

關於 FlyVPN 的啟動方式,就參考官網即可,建議先完成 FlyVPN 的連線後,再開啟 Hotspot ,例如用手機連上 Hotspot 時,可以開瀏覽器瀏覽 ipinfo.io 看看顯示的地區是否有正確更換。

流水帳 - FlyVPN :

$ ./flyvpn login

$ ./flyvpn list

$ sudo .flyvpn connect "Moscow #13"

選 tcp 即可

如此在 Ubuntu 可以另外再開個 terminal 運行 curl ipinfo.io 就得知 IP 是否有變換到莫斯科了