2019年1月26日 星期六

Chromecast 與 微型投影機 EZCast Beam J2 提供 Netflix 大螢幕投影

Chromecast + EZCast Beam J2 + Netflix

Chromecast 大家都不陌生,倒是有沒有人想一台小型、隨身投影機並且搭配 Chromecast 使用?投影機搭配 Chromecast 的好處是支援各大需要 DRM 保護的內容服務,如 Netflix、myVideo、Hami Video、CatchPlay 等線上影音服務,當 Chromecast 發現輸出 HDMI 資料的過程可能被竊聽/盜取資料時,整個輸出就會變成黑畫面的!

Chromecast + EZCast Beam J2

而使用 EZCast Beam J2 投放 HDMI 資料是符合數位內容保護機制,因此 EZCast Beam J2 微型投影機是可以搭配 Chromecast 使用,將 Netflix 等頻道內容投放出去眾樂樂。洽逢2019年初活動,入手了一台。

EZCast Beam 開箱 01
EZCast Beam 開箱 02 - 背後有規格: 3000 mAh 電池
EZCast Beam 開箱 03 - 主機 + 說明說 + 遙控器 + 電源
EZCast Beam 開箱 04 - 主機 + 遙控器 + 電源

先來提一下 EZCast Beam J2 這台定價台幣一萬元的微型投影機的定位(各大通路常有特價八折的價格)。在微型投影機的領域,常見台幣兩千以下的產品,與那些低價產品主因是硬體用料、投影技術的差別,反應在投影機亮度甚至投影機壽命。而投影機亮度常見單位是 ANSI 流明 跟 ISO 流明,兩者是計算的方式不同,而 ISO 流明數字是 ANSI 六倍左右。通常 2000 元以下的投影機,都會故意誇大自己的流明數,像是用 ISO 計算方式的流明或是胡亂標記,畢竟用戶很難認真比較、計算,建議使用上,還是參考投影機品牌的價格去推估。

EZCast Beam 開箱 05 - 散熱孔 + DLP
EZCast Beam 開箱 06 - 供電 + USB + HDMI + 耳機

就 EZCast Beam J2 而言,提供的投影機亮度是 ANSI 300流明 (約略 ISO 1800 流明)。拿 300 流明去 Google 一下,大概很容易看到破萬以上的品牌產品,如 Sony/Benq/Optoma/Acer/ASUS 等等,對等之下,EZCast Beam J2 在各大通路的特價後的八千價格,親民許多!可惜的是市場上太多低價投影機,導致萬元出頭的中階投影機容易失色於價格市場,甚至容易被誤會,這也是目前各大消費型品牌投影機碰到的困局,要嘛專注三五萬以上的高階機種,要嘛就跳下去做廉價的商品。

此外,影響投影機的價格還有:重量、體積大小。這兩個領域上 EZCast Beam J2 重量 400g / 體積 11.5公分x11.5公分x3.2公分 表現是很出色的,大多台幣1萬5以下的投影機,還很常看到 800g~1kg 等超過一瓶礦泉水的重量。

聊完一些價格上常吃悶虧的 EZCast Beam J2 後,回歸主題!當 Chromecast 搭配 EZCast Beam J2 後,可以有什麼便利的應用?當然是家裡電視不夠大,靠投影機投放到布幕上!透過微型投影機自帶 USB 插槽跟行動電源功能,可以便利地幫 Chromecast 供電,方便帶著走。

接下來就可以靠 Chromecast 提供的功能,播放 Netflix 影集等需要 DRM 保護的數位內容了。

Chromecast + EZCast Beam J2 + Youtube TWICE MV
Chromecast + EZCast Beam J2 + Youtube iKON MV

若對 EZCast Beam J2 感興趣,可以參考其他兩篇簡介及官網規格資訊,其實他根本不用靠 Chromecast 就可以完成很多無線/有線投影服務:
其他 Chromecast 相關簡介:

2019年1月25日 星期五

Google Analytics 使用筆記 - URI GROUPING

GA - 資料檢視 - 篩選器 - 搜尋與取代

這半年除了狂用 GA API 自製 Dashboard 外,偶爾也要強迫自已直接用 GA Web console 做事。在研究資料過濾/整頓時,發現 GA 的 資料檢視 已經做得很完美了,有個非常貼心的小功能稱作 篩選器 ,裏頭新增規則時可以做到 URI grouping 效果!

也就是,有些網址會帶很多參數,如:

/service?country=US
/service?country=TW
/service?country=JP&lang=

可以透過篩選器把他們都整合成一則:/service

如此,在觀看即時數據時,就可以一眼看出哪個服務最多人使用了!

使用方式:

資料檢視 -> 挑選/新建一個項目 -> 篩選器 -> 新增篩選器 -> 篩選器類型 選擇 自訂 -> 搜尋與取代

設定完後,下方還有 "驗證這個篩選器" ,很貼心

接著就能靠正規表達式子取代掉,達成 URI Grouping 效果。後續也可以透過這個篩選器,把不要看的流量去掉,讓人可以專注在該重視的項目上。

2019年1月4日 星期五

弦外之音

2019年一開始就有許多刺激的事情在進行,不少事都是冰山一角,聊完才略知弦外之音。

看來還是得好好規劃自己想走的路線,專注自己的步調。

2018年12月9日 星期日

[Linux] dev_appserver.py ERROR module.py:1652] The PHP runtime cannot be run with the "Memcache" PECL extension installed @ Ubuntu 16.04

在 Ubuntu server 上,首推 https://cloud.google.com/sdk/downloads#linux 安裝法,不要靠 apt 套件管理,不然會踩到很多麻煩:

$ curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-183.0.0-linux-x86_64.tar.gz | tar -xvf -
$ cd google-cloud-sdk
$ ./install.sh


裝完後,若是要運行 php-sdk 時,會踩到 php 環境問題,通常就是要指定 --php_executable_path 位置:

$ dev_appserver.py --enable_host_checking false --host 0.0.0.0 --port 8080  --php_executable_path /usr/bin/php-cgi5.6 app.yaml

只是執行時又會踩到缺套件等等的問題,裝到最後產生了 Memcache 套件問題,就要再把 Memcache 套件關閉即可:

$ php5.6 --ini
Configuration File (php.ini) Path: /etc/php/5.6/cli
Loaded Configuration File:         /etc/php/5.6/cli/php.ini
Scan for additional .ini files in: /etc/php/5.6/cli/conf.d
Additional .ini files parsed:      /etc/php/5.6/cli/conf.d/10-opcache.ini,
/etc/php/5.6/cli/conf.d/10-pdo.ini,
/etc/php/5.6/cli/conf.d/20-apcu.ini,
/etc/php/5.6/cli/conf.d/20-bcmath.ini,
/etc/php/5.6/cli/conf.d/20-calendar.ini,
/etc/php/5.6/cli/conf.d/20-ctype.ini,
/etc/php/5.6/cli/conf.d/20-exif.ini,
/etc/php/5.6/cli/conf.d/20-fileinfo.ini,
/etc/php/5.6/cli/conf.d/20-ftp.ini,
/etc/php/5.6/cli/conf.d/20-gettext.ini,
/etc/php/5.6/cli/conf.d/20-iconv.ini,
/etc/php/5.6/cli/conf.d/20-igbinary.ini,
/etc/php/5.6/cli/conf.d/20-json.ini,
/etc/php/5.6/cli/conf.d/20-memcache.ini,
/etc/php/5.6/cli/conf.d/20-msgpack.ini,
/etc/php/5.6/cli/conf.d/20-phar.ini,
/etc/php/5.6/cli/conf.d/20-posix.ini,
/etc/php/5.6/cli/conf.d/20-readline.ini,
/etc/php/5.6/cli/conf.d/20-shmop.ini,
/etc/php/5.6/cli/conf.d/20-sockets.ini,
/etc/php/5.6/cli/conf.d/20-sysvmsg.ini,
/etc/php/5.6/cli/conf.d/20-sysvsem.ini,
/etc/php/5.6/cli/conf.d/20-sysvshm.ini,
/etc/php/5.6/cli/conf.d/20-tokenizer.ini,
/etc/php/5.6/cli/conf.d/25-memcached.ini

$ sudo vim /etc/php/5.6/cli/conf.d/20-memcache.ini
; uncomment the next line to enable the module
;extension=memcache.so

$ sudo vim /etc/php/5.6/cli/conf.d/25-memcached.ini
; priority=25
;extension=memcached.so

2018年12月3日 星期一

Android 開發筆記 - Image Picker 與權限管理

在 Android 6.0 (API:23) 後,就算 AndroidManifest.xml 寫好要權限的部分,但實務上在進行時,依舊要主動要一次,讓用戶感受到真的要權限了:https://developer.android.com/training/permissions/requesting

做 Image Picker 時,若最終要讀取資料,那需要 android.permission.READ_EXTERNAL_STORAGE 權限,以下則是在 MainActivity 運行的範例:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>


public class MainActivity extends AppCompatActivity {
private static final int REQUEST_SELECT_VIDEO = 1;
private static final int REQUEST_EXTERNAL_STORAGE = 2;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
} else {
request_pick();
}
}

void request_pick() {
final Uri mUri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
final Intent mIntent = new Intent(Intent.ACTION_PICK, mUri);
final PackageManager mPackageManager = getPackageManager();
List<ResolveInfo> list = mPackageManager.queryIntentActivities(mIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() > 1) {
startActivityForResult(
Intent.createChooser(
new Intent(Intent.ACTION_PICK, mUri), "選取圖片"
),
REQUEST_SELECT_VIDEO
);
} else {
startActivityForResult(mIntent, REQUEST_SELECT_VIDEO);
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_SELECT_VIDEO) {
// ...
// 在處理檔案讀取時,缺少 android.permission.READ_EXTERNAL_STORAGE 會造成 IOException:
// open failed: EACCES (Permission denied)
// ...
}
}
}
}