<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[SWAG - Medium]]></title>
        <description><![CDATA[The SWAG Life - Medium]]></description>
        <link>https://medium.com/swag?source=rss----bfbe2f925f7e---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>SWAG - Medium</title>
            <link>https://medium.com/swag?source=rss----bfbe2f925f7e---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 14 Apr 2026 05:50:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/swag" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[影片編碼流程探討—以H.264編碼為例]]></title>
            <link>https://medium.com/swag/%E5%BD%B1%E7%89%87%E7%B7%A8%E7%A2%BC%E6%B5%81%E7%A8%8B%E6%8E%A2%E8%A8%8E-%E4%BB%A5h-264%E7%B7%A8%E7%A2%BC%E7%82%BA%E4%BE%8B-dd45fd6f9c7b?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/dd45fd6f9c7b</guid>
            <category><![CDATA[video-coding]]></category>
            <category><![CDATA[h264]]></category>
            <dc:creator><![CDATA[Li-shuang Yu]]></dc:creator>
            <pubDate>Tue, 03 Jan 2023 12:01:01 GMT</pubDate>
            <atom:updated>2023-01-03T12:01:01.797Z</atom:updated>
            <content:encoded><![CDATA[<p>Swag 平台上有大量的影音內容，處理過程中牽涉到影像的編碼、解碼。了解我們常用的 H.264 編碼的原理與工作流程，可以幫助我們在調教編碼器的時候，不再單純依賴直覺，而是依據編碼解碼的原理進行合理操作。網路上常見講解編碼解碼的文章都是將各部分功能條列，並對細項分頭描述。本文則從流程切入，希望能帶給讀者抽象流程的理解，更能了解網路上常見的內容的背後意涵。</p><h3><strong>概述</strong></h3><p>影像編碼的主要流程為「預測(prediction)」-&gt;「轉換(transformation)與量化(quantization)」-&gt;「熵編碼(entropy coding)」。</p><p>編碼器先用<strong>已知的資訊</strong>預測當前的影格，將當前影格分成<em>預測的資訊</em>跟<em>剩餘(residual)的資訊</em>。剩餘的圖像資訊經由轉換後，可以進行壓縮，將影響較小的資訊去除，達到大幅縮小資訊量的目的。最後，這些資訊會依據發生頻率進行編碼，在不影響內容的情況下，進一步壓低所需空間。</p><p>編碼器在轉換壓縮後，會像解碼器一樣，將壓縮的資料重新解壓縮成影格，用來當作新的已知資訊，預測下一個影格。這樣，就可以保持接收端解碼器的誤差不會越來越大，接收者可以看到一致的結果。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/797/1*G0RLSUtBlgoZWHW0W9ggtw.png" /></figure><p>另外，H.264 編碼器是將影格切為巨集區塊(macroblock, MB)處理。巨集區塊大小最主要有 16 x 16 或 4 x 4，大的區塊應對大色塊，而小的區塊處理細節畫面。一搬進行方向是由左到右、由上而下，先處理好的區塊就可以視為新的已知資訊，可用來預測後處理的區塊。</p><h3><strong>預測</strong></h3><p>巨集區塊的資訊，通常會跟同一影格鄰近巨集區塊或前後影格的像素相關。此時巨集區塊的值就可以分解成以其它區塊<em>預測的部分</em>，加上<em>預測誤差</em>。預測的部分可以簡易描述要查找的區塊。而誤差的部分則可以在之後近一步壓縮。H.264 編碼器，有 2 種預測資訊的來源：</p><h4>1. 跨影格預測(Inter prediction)</h4><p>就是用較早解碼影格的像素資訊做預測。雖說是預測，但進行方式是相反的。用當前的巨集區塊的方框去之前解碼的影格移動比對，尋找預測誤差最小的方框，如圖所示。而到最佳預測方框所需移動的向量，就是位移補償。預測後，只要紀錄位移補償與誤差，可以減少紀錄重複的像素資訊。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/322/1*ofNXorwBLV5aPGP83viHXA.png" /><figcaption>巨集區塊從之前解碼的影格比較均方誤差 (meas square error, MSE)，找出最適合的引用區塊，紀錄位移補償。此處的位移補償的向量是 (1, -2)。</figcaption></figure><p>注意較早解碼影格不一定代表影格本身時間的先後順序。巨集區塊的預測可用之前或是之後的影格，但一定是先解碼的已知資訊。因此，巨集區塊可區分為只用之前影格資訊的 P 巨集區塊 (P MB)，和兩個方向都可以的B巨集區塊 (B MB)。其中 B 巨集區塊壓縮效率較好，但也更複雜，由於影格需要重新排序，延遲也會延長。B 巨集區塊僅在主設定檔 (main profile) 提供。</p><h4>2. 影格內預測(Intra prediction)</h4><p>I 巨集區塊 (I MB)。限定用同一影格先前已編碼的巨集區塊進行預測，可能是左方巨集區塊預測，上方巨集區塊預測，或是這些巨集區塊的平均。如圖。</p><p>預測後，以哪些巨集區塊與何種平均方式預測，加上誤差，可以減少紀錄重複的像素資訊。</p><figure><img alt="巨集區塊的像素資訊會從左方或上方解碼的像素獲得。這裡顯示 9 種取樣方式的其中一種。" src="https://cdn-images-1.medium.com/max/480/1*dnklIy9duGliunmR8I-kBQ.png" /><figcaption>巨集區塊裡的像素會從左方或上方已解碼的像素預測。箭頭即預測方向。這裡顯示多種預測模式的其中一種。</figcaption></figure><p>我們常見的I影格(I frame)、P影格(P frame)以及B影格(B frame)，與上述各種巨集區塊有以下關係：<br>I 影格，只會包含 I 巨集區塊。<br>P 影格，可包含 I 巨集區塊或 P 巨集區塊。<br>B 影格，上述巨集區塊皆可包含。</p><h3>轉換與量化</h3><p>巨集區塊經過預測後，留下剩餘的資訊 (residual MB)，也就是預測誤差需要處理。首先會經過離散餘弦轉換，得出的係數再藉由量化進行壓縮。編碼器也是在此階段控制量化量以達到目標位元率 (bitrate)。</p><h4>離散餘弦轉換 (discrete cosine transform，DCT)</h4><p>預測誤差相較於原始像素強度資訊，由於像素間的相關性大幅降低，較容易處理。此時對這些資訊進行離散餘弦轉換，係數矩陣會有由左上到右下遞減的特徵。離散餘弦轉換類似傅立葉轉換 (Fourier transform)，轉換後各頻率的係數會由左上（低頻），往右下（高頻）排列。通常左上的數值最大，接著會如同扇形等高線般向右下逐步減少。這種特徵適合鋸齒形 (zigzag) 的掃描方式，形成的實數一維陣列在接下來的量化後通常會帶有一長串可省略的 0。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/191/1*kE-mtHPsPfkc-yT19AxTEw.png" /><figcaption>對巨集區塊進行鋸齒形掃描。</figcaption></figure><h4>量化</h4><p>轉換後接著就是以量化減少資訊。量化是將這些實數數值改以有限的整數取代。會丟失小數或是餘數的資訊，因此才會減少資訊量。量化時使用的數值越少，損失越多。編碼器在操作的時候，則是使用量化參數 (quantization parameter, QP)，量化參數越大代表量化時每一階跨度越大，可視為使用的數值越少，損失越多。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/631/1*l9E800A69APCauJutNXVGg.png" /><figcaption>原值與量化後的對照，可以看到當量化參數為 2 的時候，剩下 5 階的數值。當量化參數為 5 的時候，階數只剩下 3。</figcaption></figure><p>如同前面所說，由其在高頻的部分，許多原本趨近於零的數量化後會呈現連續的 0。連續的 0 可以用運行長度編碼 (run-length encoding) 來進一步減少所需的空間。如果選擇量化量高，則丟失的資訊越多，壓縮率越高。編碼器在尋求滿足目標位元率時，會動態決定使用的量化參數來達成目標。</p><p>轉換與量化完成後，編碼器會將壓縮的資訊，先乘上量化量，再進行逆離散餘弦轉換 (inverse discrete cosine transform)，解壓縮還原成預測誤差。解壓縮的預測誤差，加上上一個步驟的預測資訊，就還原成解碼的影格。可以用來預測下一個要處理的巨集區塊。由於經過量化壓縮，解壓縮的預測誤差已經與壓縮前的預測誤差不同，也就是失真了。</p><h3>熵編碼</h3><p>影格經過處理後，產生的資訊也可以經過內容編碼再進一步縮減所需空間。其原則是發生頻率越高的內容用越短的碼字 (code word) 表示。H.264 支援 2 種編碼方式：</p><h4>適應性可變長度編碼 (Context-based Adaptive Variable-Length Coding, CAVLC)</h4><p>是一種可變長度編碼(variable length coding)。其對應碼字的推論是基於霍夫曼編碼(Huffman coding)。不過實際上 H.264 有提供現成的編碼表，大部分清況下編碼器只需參照編碼即可，並不以自己計算產生專屬的編碼表。此編碼方式較基礎，碼字的長度皆為整數，如果遇到某個值發生頻率高於50%的情況下，受限於碼字不能小於1的情況，壓縮效率較差，也沒有處理傳輸錯誤的情況。適應性可變長度編碼是基線設定檔 (baseline profile) 使用的熵編碼方式。</p><h4>適應性二元算術編碼 (Context-based Adaptive Binary Arithmetic Coding, CBABC)</h4><p>是以算術編碼(arithmetic coding)為基礎。此編碼計算方式較複雜，僅主設定檔提供支援，但能較適應性可變長度編碼更接近理論壓縮極限，壓縮效率較佳。</p><h3>Apple VideoToolbox 的相關設定</h3><p>檢視 Apple 的 VideoToolbox 裡面跟 H.264 相關的介面，<em>VTCompressionProperties.h</em>。我們可以尋找跟本文主題相關設定。</p><p>可選擇設定檔(profile)，從基線設定檔、主設定檔與高設定檔(high profile)。</p><pre>VT_EXPORT const CFStringRef kVTCompressionPropertyKey_ProfileLevel;<br><br>VT_EXPORT const CFStringRef kVTProfileLevel_H264_Baseline_1_3;<br>VT_EXPORT const CFStringRef kVTProfileLevel_H264_Baseline_3_0;<br>...<br>VT_EXPORT const CFStringRef kVTProfileLevel_H264_ConstrainedHigh_AutoLevel;</pre><p>像下面這是與設定是否使用 B 影格相關。</p><pre>VT_EXPORT const CFStringRef kVTCompressionPropertyKey_AllowFrameReordering;</pre><p>如同我們講到，編碼器量化參數不提供直接設定，而是設定平均位元率，交由編碼器決定。</p><pre>VT_EXPORT const CFStringRef kVTCompressionPropertyKey_AverageBitRate;</pre><p>支援本文所提的兩種熵編碼的方式。</p><pre>VT_EXPORT const CFStringRef kVTCompressionPropertyKey_H264EntropyMode;<br><br>VT_EXPORT const CFStringRef kVTH264EntropyMode_CAVLC;<br>VT_EXPORT const CFStringRef kVTH264EntropyMode_CABAC;</pre><p>文件在解釋適應性二元算術編碼時，提醒我們應選擇支援的設定檔，避免產生錯誤結果。然而，在具備了對編碼器的知識後，這種設定錯誤就不容易產生。</p><pre>/**<br>...<br>Care should be taken when using this property -- changes may result in a configuration<br>  which is not compatible with a requested Profile and Level.  Results in this case are undefined,<br>  and could include encode errors or a non-compliant output stream.<br>*/<br></pre><h3>結語</h3><p>經過「預測」-&gt;「轉換」-&gt;「熵編碼」的處理，影格資料基本上就完成編碼輸出了。解碼器將拿到的資訊進行逆向處理，就可以解碼還原成影格顯示了。解碼器只要依據指示進行逆運算，相較之下是較簡單，可在運算力較差的裝置執行。</p><p>對比之下，編碼器在各階段要依據使用者要求做出適合的組合，像是預測階段要選擇預測方式、搜尋範圍內適合預測的巨集區塊，轉換階段要選擇適合的量化參數，熵編碼也要選擇適合的編碼表，這樣的決策組合可多達上百種，也就需要更有力的機器。</p><p>我們在進行存擋影片或直播影片編碼參數調教時，可以藉由對編碼器較深入的認識，選擇適當的組合。例如直播時，可以關閉 B 預測模式縮短延遲。在接收端運算能力充足的情況下，可以選擇主設定檔以使用壓縮效率更好的適應性二元算術編碼等。另外我們也會知道當我們調低目標位元率時，編碼時會傾向用更高的量化參數，也就代表犧牲更多畫質，來達成目的。</p><p><em>參考資料</em><br>Richardson, Iain. E. G. The H.264 advance video compression standard: Wiley, 2010.</p><p><em>名詞翻譯參考</em><br><a href="https://terms.naer.edu.tw/">國家教育研究院—樂詞網</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd45fd6f9c7b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/%E5%BD%B1%E7%89%87%E7%B7%A8%E7%A2%BC%E6%B5%81%E7%A8%8B%E6%8E%A2%E8%A8%8E-%E4%BB%A5h-264%E7%B7%A8%E7%A2%BC%E7%82%BA%E4%BE%8B-dd45fd6f9c7b">影片編碼流程探討—以H.264編碼為例</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Presentations Day #1]]></title>
            <link>https://medium.com/swag/presentations-day-1-e4eaf8d97571?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/e4eaf8d97571</guid>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[presentations]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Allan Lei]]></dc:creator>
            <pubDate>Thu, 24 Feb 2022 04:09:34 GMT</pubDate>
            <atom:updated>2022-02-24T04:09:34.796Z</atom:updated>
            <content:encoded><![CDATA[<h4>Winter 2021</h4><p>不久之前，RD的夥伴們完成了一場精彩絕倫的分享！ 這次有四位同仁分享覺得有趣的主題，分別為：</p><ul><li>Lego CI/CD with github action</li><li>The wet codebase</li><li>Enlarge eyes by Apple face detection and Metal shader</li><li>Smooth skin principle and implementation</li></ul><p>快來看看這次的Presentations吧！</p><h3>Enlarge eyes by Apple face detection and Metal shader</h3><ul><li>Introduce Metal and compare to OpenGL ES</li><li>Introduce Vision</li><li>Enlarge eyes algorithm</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FP8MtZadt8Fs%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DP8MtZadt8Fs&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/473fc8ebcdcb19bbe464790becfce1e9/href">https://medium.com/media/473fc8ebcdcb19bbe464790becfce1e9/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1iwpEVZXekjWRKPskZ9Pfra6xsw0-ceZLFB5xxRVfiSQ%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1iwpEVZXekjWRKPskZ9Pfra6xsw0-ceZLFB5xxRVfiSQ%2Fedit%3Fusp%3Dsharing&amp;image=https%3A%2F%2Flh6.googleusercontent.com%2Fko4UnMk11OLKiR7e6b6JyRcHB8MV6VhLyxVuLSIwxlIvNTAM2LzHEuqCGsg_ql9_jkZXEMjMHGGE-A%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ff6fbca6d98f4b8101799d3b46a6065/href">https://medium.com/media/9ff6fbca6d98f4b8101799d3b46a6065/href</a></iframe><h3>Smooth skin principle and implementation</h3><p>本篇介紹 GPUImage 中一個常用的美肌濾鏡，並深入解釋其中使用高斯模糊濾鏡的原因及原理。</p><ul><li>Overview of filter pipeline</li><li>Box blur vs. Gaussian blur and how to implement with shader</li><li>Why Gaussian blur is suitable for smooth skin (fourier transform, low-pass filter)</li><li>Filter pipeline principle summary</li><li>Direction to improve performance and combine with face detection</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FMLGhE64gkWQ%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DMLGhE64gkWQ&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FMLGhE64gkWQ%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2869ba03164e7bff9578f474cc0c2fe6/href">https://medium.com/media/2869ba03164e7bff9578f474cc0c2fe6/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1N4Od8XfDZn93oMd1r3-Wnv_cbxPhiyqIRQjxo0eOurU%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1N4Od8XfDZn93oMd1r3-Wnv_cbxPhiyqIRQjxo0eOurU%2Fedit%3Fusp%3Dsharing&amp;image=https%3A%2F%2Flh6.googleusercontent.com%2Fg4yR7TlTESidymmJzSX6iMcMeRiyuhBugLg5uyl2JaDPRqGxD8HxYKLqJQQkEKhDXSVHNgDplbm4OQ%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/e678e7542e23d32ddaaaa98f7f773e68/href">https://medium.com/media/e678e7542e23d32ddaaaa98f7f773e68/href</a></iframe><h3>Lego CI/CD with Github Actions</h3><p>Using features on Github (Github Action, Github App, Github Deployment) implement a GitOps flow which make application developer and DevOps engineer cowork with each other easier, and how this flow improve the development experience.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FAXlIFU0PeA0%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DAXlIFU0PeA0&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/513b308b0f2092f2b9a10ddcba115736/href">https://medium.com/media/513b308b0f2092f2b9a10ddcba115736/href</a></iframe><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fembed%3Fid%3D1-l31Z6MDAE1HfoMy9JyUcKMVghskdozXSm5BKMASW_s%26size%3Dl&amp;display_name=Google+Docs&amp;url=https%3A%2F%2Fdocs.google.com%2Fpresentation%2Fd%2F1-l31Z6MDAE1HfoMy9JyUcKMVghskdozXSm5BKMASW_s%2Fedit%3Fusp%3Dsharing&amp;image=https%3A%2F%2Flh5.googleusercontent.com%2F7NkI9JN4lNSZfGqqOEybno-muFkz34Y7zbsn__1yJb94sFKMPIZCNPUMksnlFs2enqPhf_Kvpf7xZA%3Dw1200-h630-p&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=google" width="700" height="559" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b32860b4a9abf88df7ec5a4b81aea18/href">https://medium.com/media/1b32860b4a9abf88df7ec5a4b81aea18/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4eaf8d97571" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/presentations-day-1-e4eaf8d97571">Presentations Day #1</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Blur Out Videos with FFmpeg]]></title>
            <link>https://medium.com/swag/blur-out-videos-with-ffmpeg-92d3dc62d069?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/92d3dc62d069</guid>
            <category><![CDATA[media-processing]]></category>
            <category><![CDATA[ffmpeg]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Allan Lei]]></dc:creator>
            <pubDate>Tue, 11 Jan 2022 08:19:56 GMT</pubDate>
            <atom:updated>2019-09-07T08:33:44.909Z</atom:updated>
            <content:encoded><![CDATA[<h4>Or how to utilize filter_complex</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*SKR4tOqHQ7KVKkKFQ57yxA.gif" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*6yiVdG4NBk5687mrQe4t2w.gif" /><figcaption>Original vs Blur Out</figcaption></figure><h3>Harnessing filter_complex</h3><p>FFmpeg’s filter_complex works in a similar fashion as <a href="https://www.tldp.org/LDP/GNU-Linux-Tools-Summary/html/c1089.htm">Unix pipes</a>. Take a input, modify, output, then rinse and repeat. A filtergraph contains one or more filterchains, each which contains a certain order of filters to be applied to the input source. Like Unix pipes, you can get pretty creative and generate some great results.</p><h4>Splitting Outputs</h4><p>First, we will split a single input into 2 outputs with scaled resolutions.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/75a9180e10682d192c9dd09cc4e07ac4/href">https://medium.com/media/75a9180e10682d192c9dd09cc4e07ac4/href</a></iframe><ul><li>0:v: Select the first input source’s videostream only</li><li>split=2: Split the stream into 2 and store them as 360pand 720p</li><li>[360p]scale=-2:360[360p] and [720p]scale=-2:720[720p] scales the input to 360px and 720pxheight respectively while preserving aspect ratio and sends the output back to the same input name. The -2 is to tell the scaler to scale to a even number as some formats do not support a odd number of pixels</li><li>-map &quot;[360p]&quot; 360p.mp4 and -map &quot;[720p]&quot; 720p.mp4 take each respective output and encodes it to a file.</li></ul><p>The result will be 2 files with different resolutions. Keep in mind, all outputs created in the filtergraph must be connected to an output.</p><h4>Blurring</h4><p>The next component is to generate a blurred video. There are many blurring algorithms available, but for this task, we will be using <a href="https://ffmpeg.org/ffmpeg-filters.html#boxblur">boxblur</a>. This blur has 3 tunable settings each with radius (box radius to apply to the frame) and power (how many times to apply to the frame):</p><ul><li>luma (luma_radius and luma_power): Brightness</li><li>chroma (chroma_radius and chroma_power): Color</li><li>alpha (alpha_radius and alpha_power): Transparency</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ef3854acd203ad5edee5420957fc70f1/href">https://medium.com/media/ef3854acd203ad5edee5420957fc70f1/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*SKR4tOqHQ7KVKkKFQ57yxA.gif" /><figcaption>Original</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*arm62-mG8ncLW_dbR_z0tw.gif" /><figcaption>luma_radius=10chroma_radius=10:luma_power=1</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*x5QlT5qejWbo6eCNSONXjg.gif" /><figcaption>luma_radius=50:chroma_radius=25:luma_power=1</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*yrg1FR2a1dJ_omXn1VgQqg.gif" /><figcaption>luma_radius=min(w\,h)/5:chroma_radius=min(cw\,ch)/5:luma_power=1</figcaption></figure><h4>Fading In</h4><p>The last part we need is a fade. This is filter is fairly simple as it takes a start and a end with either a fade in or fade out. It supports both frames and time/duration. For our case, we will use the time/duration and skip calculating frames.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab88a792cf098797d1dae17bccc278cc/href">https://medium.com/media/ab88a792cf098797d1dae17bccc278cc/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*SKR4tOqHQ7KVKkKFQ57yxA.gif" /><figcaption>Original</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*OUl9Al9Y0-rkAEAKyCS3CA.gif" /></figure><h3>Peanut Butter Jelly Time</h3><p>Now that we have all the tools we need, let’s put this all together. The plan is to put each of the filters above to create a Blur Out effect. A Blur Out is essentially 2 videos (the original and a blurred version) overlayed on top of each other with the one of the videos fading.</p><p>For this example, we will choose the original video as the baseand the blurred video as the fade in.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b625cc4d20f8b6fd8fada384a63bb705/href">https://medium.com/media/b625cc4d20f8b6fd8fada384a63bb705/href</a></iframe><ol><li>We will will need to split the input into 2, base and blurred, so we use the split filter</li><li>Next we need to generate a blurred version using boxblur</li><li>Taking the blurred input, we add a fade in start time 1s and duration 3s. The extra alpha=1 mentioned here is to allow the video to be transparent when faded out else the background color will be black</li><li>The final step is to use overlay to put input sources on top of each other (like an onion). The alpha=1 from above would then allow the base to show through the blurred layer as it is being faded in.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/1*6yiVdG4NBk5687mrQe4t2w.gif" /><figcaption>Final results</figcaption></figure><h4>References</h4><ul><li><a href="https://trac.ffmpeg.org/wiki/FilteringGuide">FFmpeg Filtering Guide</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=92d3dc62d069" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/blur-out-videos-with-ffmpeg-92d3dc62d069">Blur Out Videos with FFmpeg</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Telepresence: Rapid Deployment and Communicate with Remote Kubernetes Cluster in Local]]></title>
            <link>https://medium.com/swag/telepresence-rapid-deployment-and-communicate-with-remote-kubernetes-cluster-in-local-1ba554732afb?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/1ba554732afb</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Rammus]]></dc:creator>
            <pubDate>Wed, 05 Jun 2019 16:57:11 GMT</pubDate>
            <atom:updated>2020-06-29T02:33:27.159Z</atom:updated>
            <content:encoded><![CDATA[<p>Telepresence 透過 <a href="https://github.com/sshuttle/sshuttle">sshuttle</a> 使用 SSH connection 產生 VPN-like tunnel，建立一個雙向的 network proxy。(<a href="https://www.telepresence.io/discussion/how-it-works">more details</a>)，甚至可以在 service 前面加一層 ingress, cert-manager 進而產生一個 Local HTTPS 的開發環境。</p><p>此外，<a href="https://www.cncf.io/">CNCF 基金會</a> 目前已經將 <a href="https://www.telepresence.io/">Telepresence</a> 加入計畫，可以對這個工具多一點的信心。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n9z_pC_YQkxit7UPpi-T2w.png" /></figure><h3>解決問題</h3><ul><li>如果 cluster 的 micro service 很多，不可能在本地端用 minikube 測試。</li><li>不需要等待 CI/CD 將程式碼推到 Cluster 才能看到結果</li><li>不需要額外設定 VPN(OpenVPN, Wireguard)，存取 Cluster 其他 Service</li><li>可以在程式內直接使用 Kubernetes Cluster 的 Service. ex. requests.get(‘client.elasticsearch:9200’)</li></ul><h3><strong>Step by Step</strong></h3><p>Code: <a href="https://github.com/RammusXu/toolkit/tree/master/demo/k8s/telepresence">https://github.com/RammusXu/toolkit/tree/master/demo/k8s/telepresence</a></p><p>Install</p><pre>brew cask install osxfuse</pre><pre>brew install datawire/blackbird/telepresence</pre><p>先準備一個 telepresence 的 proxy pod(deployment+service)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7edee7a793a6396f93be6f693d1d968d/href">https://medium.com/media/7edee7a793a6396f93be6f693d1d968d/href</a></iframe><p>以及一個簡單的 Flask</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f2e79e6173b89c7be8d5a9ac0af64c64/href">https://medium.com/media/f2e79e6173b89c7be8d5a9ac0af64c64/href</a></iframe><p>Run</p><pre>kubectl apply -f telepresence.yaml</pre><pre>sudo telepresence --deployment telepresence-rammus --expose 5000:80 --namespace dev --run sh -c &quot;FLASK_DEBUG=1 flask run&quot;</pre><pre>`--deployment [deployment-name]` 將會置換掉 deployment</pre><pre>`--expose 5000:80` 將 localhost:5000 expose 到 kubernetes-pod:80</pre><pre>`--namespace [your-namespace]` 可以選擇要替換哪個 namespace 的 deployment。預設是 `default`</pre><pre>`--run` 啟用 server 的方式。ex. `npm start`, `flask run`, `rails server`。這邊帶入 FLASK_DEBUG=1 自動監控 app.py 程式碼變更時重啟 server，如果是別的語言，請自行替換，或是加入環境變數。</pre><h3><strong>Advanced</strong></h3><p>如果需要限制 developer 的 RBAC，這裡有 minimal RABC role：https://www.telepresence.io/reference/connecting#running-telepresence-manually</p><p>如果想要用 docker 可以參考：https://www.telepresence.io/tutorials/docker</p><pre>telepresence — swap-deployment hello-world — docker-run — rm -it -v $(pwd):/usr/src/app hello-dev</pre><p>如果不希望 developer 的 pod 可以存取所有其他的 service/pod，可以參考：</p><ul><li><a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">https://kubernetes.io/docs/concepts/services-networking/network-policies/</a></li><li><a href="https://github.com/ahmetb/kubernetes-network-policy-recipes">https://github.com/ahmetb/kubernetes-network-policy-recipes</a></li></ul><h3><strong>其他選擇</strong></h3><ul><li><a href="https://github.com/GoogleContainerTools/skaffold">skaffold</a></li><li><a href="https://github.com/vapor-ware/ksync">Ksync</a></li></ul><h3><strong>Reference</strong></h3><ul><li><a href="https://www.reddit.com/r/kubernetes/comments/aapysv/whats_the_dev_workflow_with_k8s_on_the_local/">https://www.reddit.com/r/kubernetes/comments/aapysv/whats_the_dev_workflow_with_k8s_on_the_local/</a></li><li><a href="https://www.bizety.com/2019/02/13/kubernetes-dev-tools-codeready-skaffold-draft-squash-telepresence-and-ksync/">https://www.bizety.com/2019/02/13/kubernetes-dev-tools-codeready-skaffold-draft-squash-telepresence-and-ksync/</a></li></ul><p>本篇文章同步發表於 <a href="https://rammusxu.github.io/">rammusxu.github.io</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ba554732afb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/telepresence-rapid-deployment-and-communicate-with-remote-kubernetes-cluster-in-local-1ba554732afb">Telepresence: Rapid Deployment and Communicate with Remote Kubernetes Cluster in Local</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[哈囉 Flutter for web！]]></title>
            <link>https://medium.com/swag/%E5%93%88%E5%9B%89-flutter-for-web-9d45261f5b5b?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/9d45261f5b5b</guid>
            <dc:creator><![CDATA[Luke Huang]]></dc:creator>
            <pubDate>Wed, 22 May 2019 11:52:45 GMT</pubDate>
            <atom:updated>2019-05-22T11:52:45.653Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CmZGc7VSJJ8xtHN8y_7guA.png" /><figcaption><a href="https://flutter.dev/">Flutter — Beautiful native apps in record time</a></figcaption></figure><p><a href="https://events.google.com/io/">Google I/O 2019</a> 最讓 WEB 開發者注意的事情就是宣佈了 <a href="https://flutter.dev/web">Flutter for web</a> 第一個技術預覽版本，同時釋出 <a href="https://medium.com/flutter-io/announcing-flutter-1-5-d203c6072e5c">Flutter 1.5</a> 版本。</p><h4>Quick Review — 什麼是 Flutter ?</h4><p>先來看看 Flutter 的官方網站對自己的形容：</p><blockquote>“Flutter is Google’s portable UI toolkit for building beautiful, native applications for mobile, web, and desktop from a single codebase.”</blockquote><p><a href="https://flutter.dev/">Flutter</a> 是一個由 Google 開發、開源及免費的跨平台行動應用程式框架，可以快速的開發 Android 及 iOS 原生使用者介面應用程式，而且可以相容於現有的專案，使用 <a href="https://dart.dev/">Dart</a> 為開發的程式語言。開發人員可以透過單一套程式碼，編譯後同時運行在 Android 及 iOS 等各平台，並且使用由 Flutter 自己的渲染引擎繪製圖層、widget，保持各平台 UI 的一致性，Flutter 也提供了許多元件、應用介面、快速成長的生態系及網路社群，可以快速的開發原生套件，提供使用者良好的體驗。Flutter 在 2019 年的重點是將 Flutter 擴展到行動應用平臺之外，如網頁、桌面及嵌入式的平台上。</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F5VbAwhBBHsg%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D5VbAwhBBHsg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F5VbAwhBBHsg%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c7d45b713dbdba30212a7d85e144b748/href">https://medium.com/media/c7d45b713dbdba30212a7d85e144b748/href</a></iframe><h4>Quick Review — 什麼是 Dart？</h4><p><a href="https://dart.dev/">Dart</a> 是 Google 開發的程式語言，於2011年10月份釋出，可以被用於 WEB、伺服器、行動端或 IoT 等領域的開發。更詳細 Flutter 為何選擇 Dart 為程式語言的說明可參考 <a href="https://flutter.dev/docs/resources/faq#why-did-flutter-choose-to-use-dart">Why did Flutter choose to use Dart</a> 有更詳細的說明。</p><h4>Quick Review — 什麼是 Flutter for web？</h4><p><a href="https://flutter.dev/web">Flutter for web</a> 是 Flutter 為實現代碼兼容 (code-compatible) 的一個實作，項目之前的名稱為 <a href="https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8">Hummingbird</a>，透過基於現有的技術標準 HTML/CSS 及 JavaScript ，自己製作渲染引擎，Flutter for web 開發者可以將使用 Dart 編寫的 Flutter 程式編譯至客戶端體驗 (client experience) 並且嵌入瀏覽器，部署至 Web。且可以使用 Flutter 現有的功能，且不需要其他瀏覽器的套件。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D5HYFARy1U-T4E9OtK3LMA.png" /><figcaption>Flutter for web Architecture.</figcaption></figure><p>由 Flutter for web 的架構可知，為了增加在瀏覽器的支援與實作，Flutter 的核心是基於的 WEB API 的標準上透過綜合 DOM、Canvas 及 CSS 的技術繪製圖層，是以 Dart 程式語言完成，使用 Dart 優化過的 JavaScript 編譯器將 Flutter 框架核心與使用者的 .dart程式編譯成新的單一、簡化的應用程式，且可以部署到任何伺服器。，提供了可攜式、高品質且高性能的使用者體驗。完整說明可參考：<a href="https://flutter.dev/web">https://flutter.dev/web</a></p><p><a href="https://flutter.dev/web">Web support for Flutter</a></p><h3>來一個 Flutter for web 的 Hello World 吧！</h3><blockquote>如果是全新的開發者請參考 Preset 步驟先安裝環境</blockquote><h4>Preset I — 安裝 Flutter 與 Dart</h4><p>參考官網的安裝流程，根據開發者的作業系統有不同安裝跟設定的方式。參考網址：<a href="https://flutter.dev/docs/get-started/install">https://flutter.dev/docs/get-started/install</a></p><h4>Preset II — 編輯器設定</h4><p>官方也提供了Android studio/IntelliJ 及 VS Code 編輯器 Dart 及 Flutter 的擴充套件，如果已經安裝過 Flutter 的開發環境，則需要更新至<strong> Flutter 1.5</strong> 版本。參考網址：<a href="https://flutter.dev/docs/get-started/editor?tab=vscode">https://flutter.dev/docs/get-started/editor?tab=vscode</a></p><pre>$ flutter upgrade</pre><p>接著透過 flutter doctor 指令檢查開發環境。會得到類似下列的結果：</p><pre>$ flutter doctor<br>Doctor summary (to see all details, run flutter doctor -v):<br>[✓] Flutter (Channel stable, v1.5.4-hotfix.2, on Mac OS X 10.14.4 18E226, locale zh-Hant-TW)<br>[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)<br>[✓] iOS toolchain - develop for iOS devices (Xcode 10.2.1)<br>[✓] Android Studio (version 3.4)<br>[✓] VS Code (version 1.33.1)<br>[!] Connected device<br>    ! No devices available</pre><pre>! Doctor found issues in 1 category.</pre><p>接著需要安裝 webdev，Dart 的 command-line tool 編譯 .dart 程式。記得要先安裝 <a href="https://pub.dev/">pub</a> (Dart Package Manager) 及一定要設定環境變數 $Home/.pub-cache/bin 不然安裝webdev會出現錯誤。</p><blockquote>如果是沒有 Dart 的環境，需要另行參考： <a href="https://dart.dev/get-dart">Dart 安裝方式</a></blockquote><p>設定完後執行安裝指令：</p><pre>$ flutter packages pub global activate webdev<br># or<br>$ pub global activate webdev</pre><h4>建立專案</h4><p>如果是使用 <a href="https://code.visualstudio.com/">Visual Studio Code</a>，可以將 VS code 上的 Dart 與 Flutter 擴充套件，更新至 3.0 版，增加了 Flutter：New Web Project 的 指令，讓我們可以自動建立 Flutter 的 web 專案。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*duI1s7Ngnm2CnL0Ttzs8Wg.png" /><figcaption>Create new flutter project via VS Code command.</figcaption></figure><p>Flutter 的專案名稱建議是以<strong>底線</strong>分開的命名規則，建立好的專案結構：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/1*f2q-18yKlDYgQi-PS6nWNw.png" /></figure><ul><li>lib 資料夾存放我們撰寫的 Dart 程式。main.dart 是整個專案程式的進入點。</li><li>web 存放網頁的檔案，如果是在 App 的專案，則會有iOS及Android 的資料夾。</li><li>pubspec.yaml 是紀錄專案的設定檔，類似於 Node.js NPM 的package.json 或 PHP Composer 的 composer.json 。</li><li>相關 asset 的設定（如：圖片、字型檔）檔案是放在 web資料夾內，也需要在 pubspec.yaml 寫相關的連結。</li><li>build資料夾則是放置程式編譯完成時產生的檔案。</li></ul><h4>執行專案</h4><p>設定好專案、安裝好相關依賴後，接下來執行 webdev serve 。</p><pre>$ webdev serve</pre><pre># or serve with hot reload<br>$ webdev serve --auto restart</pre><p>打開瀏覽器 <a href="http://localhost:8080`">http://localhost:8080</a> 就可以看到充滿 material design ？的 Flutter demo 網頁了！Hello World!</p><p>更多關於 webdev 的指令：<a href="https://github.com/dart-lang/webdev">https://github.com/dart-lang/webdev</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1RrAAY5kaHuH6qqYd8EA3Q.png" /><figcaption>Hello World localhost page.</figcaption></figure><p>最後使用打包的指令，最終檔案會編譯在 build 資料夾內。</p><pre>$ webdev build</pre><p>如果不是透過 CLI 工具建立的專案，則需要建立下列檔案（範例程式參考 CLI 所建立之檔案內容）：</p><ul><li>pubspec.yaml</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a79f5bb8875a6428b42757ed2f512bd0/href">https://medium.com/media/a79f5bb8875a6428b42757ed2f512bd0/href</a></iframe><ul><li>lib/main.dart</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/558af4414cf61f668a3d21c78dd6db10/href">https://medium.com/media/558af4414cf61f668a3d21c78dd6db10/href</a></iframe><ul><li>web/index.html &amp; web/main.dart</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/28ed6c42221f6776c7b179dc689010d5/href">https://medium.com/media/28ed6c42221f6776c7b179dc689010d5/href</a></iframe><p>執行 pub get 安裝相關的 dependencies，並且會產生.dart_tool 資料夾存放及 lock 檔案 pubspec.lock（類似 Node.js 的 node_modules &amp; package-lock.json）。</p><h4>目前的一些技術限制</h4><blockquote>Flutter for web is currently available as a technical preview.</blockquote><p>由於這個技術還在開發階段，所以還有以下問題。</p><ul><li>目前還是沒有一個好的 debug tool。</li><li>瀏覽器支援度還不夠，建議先使用 chromium based 的瀏覽器。</li><li>正在整合的過程中，以後還是有可能需要大改程式碼。</li><li>由於目前還沒有辦法直接跟 App project 直接共用程式碼，Flutter API 與 Flutter for web 的 API 不相容，不過已經正在 merge。</li><li>Flutter for web 的 dependencies 中使用的 UI SDK 與 App project 的不同。如： flutter_web ＆ flutter_web_ui</li></ul><blockquote><a href="https://github.com/flutter/flutter_web/blob/master/docs/faq.md">https://github.com/flutter/flutter_web/blob/master/docs/faq.md</a></blockquote><p>官方 Github 中也列了一份 <em>Q&amp;A</em> 說明在現在實驗性質的專案可能遇到的問題，讓開發者在踩雷的時候 (?)，可以…認命點吧。</p><h4>參考資料</h4><ol><li><a href="https://developers.googleblog.com/2019/05/Flutter-io19.html">https://developers.googleblog.com/2019/05/Flutter-io19.html</a></li><li><a href="https://flutter.dev/web">https://flutter.dev/web</a></li><li><a href="https://github.com/flutter/flutter_web">https://github.com/flutter/flutter_web</a></li><li><a href="https://medium.com/flutter-io/bringing-flutter-to-the-web-904de05f0df0">https://medium.com/flutter-io/bringing-flutter-to-the-web-904de05f0df0</a></li><li><a href="https://medium.com/flutter-community/flutter-web-announced-in-google-io19-c6f1da207f3c">https://medium.com/flutter-community/flutter-web-announced-in-google-io19-c6f1da207f3c</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d45261f5b5b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/%E5%93%88%E5%9B%89-flutter-for-web-9d45261f5b5b">哈囉 Flutter for web！</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Firebase Dynamic Link — 簡單介紹]]></title>
            <link>https://medium.com/swag/firebase-dynamic-link-%E7%B0%A1%E5%96%AE%E4%BB%8B%E7%B4%B9-fd8259974eec?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/fd8259974eec</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[firebasedynamiclinks]]></category>
            <category><![CDATA[universal-links]]></category>
            <dc:creator><![CDATA[Jacky810124]]></dc:creator>
            <pubDate>Fri, 03 May 2019 04:19:34 GMT</pubDate>
            <atom:updated>2019-05-03T04:19:34.560Z</atom:updated>
            <content:encoded><![CDATA[<h3>Firebase Dynamic Link — 簡單介紹</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KSwrRSczAE4y1DUm1k4MSA.jpeg" /></figure><p>使用者在社群中分享網頁連結，當其他人看到並且有安裝 Mobile App 時，會希望使用者能夠自動開啟 Mobile App；或是尚未安裝時，能夠自動導到 App Store 或是 Play Store。</p><p>Firebase Dynamic Link 可以讓這件事情變得簡單點，先來看一下介紹影片。</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FLvY1JMcrPF8%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DLvY1JMcrPF8&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FLvY1JMcrPF8%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/ac54579ee62d7ef9cf8656254d018d9a/href">https://medium.com/media/ac54579ee62d7ef9cf8656254d018d9a/href</a></iframe><p>看完之後是不是很心動呢？接下來介紹一下 Firebase Dynamic Link 吧！</p><h3>創建 Dynamic Link</h3><p>創建 Firebase Dynamic Link 可以透過幾種方式來創建：</p><ul><li>Firebase Console</li><li>REST API</li><li>SDK</li><li>手動創建</li></ul><p>大家可以挑選適合的方式來創建，今天將會著重在<strong>手動創建</strong>這個方式的小細節。</p><p>在預設情況下，Firebase 提供 page.link 這個域名供使用者使用，能夠自行設定不同的子網域來區分不同的用途，像是：</p><pre>// SWAG 專用的 Dynamic Link<br>swag.page.link</pre><pre>// SWAG 活動專用的 Dynamin Link<br>swagevent.page.link</pre><p>若使用者想使用自己的網域名稱，也可以在 Firebase Console 中來設定，使用自訂的網域名稱，大概會長得像這樣：</p><pre>link.swag.live</pre><p>這個部分就看實際使用的情境來決定。</p><h3>手動創建</h3><p>Firebase Dynamic Link 可以透過自行組合參數來創建，像是：</p><pre>https://link.swag.live?link=<strong>your_deep_link</strong>&amp;apn=<strong>android_package_name</strong></pre><p>若使用者有安裝對應的 Android App 則會直接開啟 Android App；若無，則會自動導到 Play Store。</p><p>Firebase Dynamic Link 的參數會分成幾個部分：</p><ul><li>Deep Link Parameters</li><li>Android Parameters</li><li>iOS Parameters</li><li>Other Platform Parameters</li><li>Navigation Parameters</li><li>Social Meta Tag Parameters</li><li>Analytics Parameters</li><li>Debug Parameters</li></ul><h4>Deep Link Parameters</h4><p>Deep Link 的參數主要是提供 App 和退到網頁版來使用，若使用者已安裝 Mobile App 則直接透過 App 來開啟；若尚未安裝，則看是要導到商店或是退到網頁版。</p><p><strong>link<br></strong>Mobile App 將會開啟的 URL，指定 App 可以處理的 URL，一般來說是 App 的內容或是啟動特定 App 邏輯的 Payload；link 會是格式良好且 URL 編碼過的 URL，可以是 HTTP 或 HTTPS，且不能為另一個 Firebase Dynamic Link。</p><p>在桌面裝置的瀏覽器上開啟時，將會開啟這個連結，除非有另外指定 ofl。</p><h4>Android Parameters</h4><p>這部分的參數主要是提供給 Android 來使用。</p><p><strong>apn<br></strong>Android 用來開啟連結的 Package name，且該 App 在 Firebase Console 的 Overview 頁面中，應該要與專案被連結在一起；對於 Dynamic Link 要開啟 Android App 來說是必要的。</p><p><strong>afl<br></strong>當 Android App 尚未安裝時所開啟的連結，這個連結用來指定一些像是開啟手機版的網頁，或是一些 App 的促銷頁面，並非用在使用者尚未安裝 App 時，從 Play Store 安裝 App 的用途。</p><p><strong>amv<br></strong>App 能夠開啟此連結的最低版本，如果安裝的較舊的版本時，將會導至 Play Store 來升級 App。</p><h4>iOS Parameters</h4><p>這部分的參數主要提供給 iOS 和 iPads 來使用。</p><p><strong>ibi<br></strong>用來開啟連結的 iOS App Bundle ID，該 App 應該要在 Firebase Console 的 Overview 中與專案連結在一起；對於 Dynamic Link 要開啟 iOS App 來說是必要的。</p><p><strong>ifl<br></strong>當 iOS App 尚未安裝時所開啟的連結，用來指定一些像是開啟手機版的網頁，或是開啟 App 的促銷頁面，並非用在使用者尚未安裝 App 時，開啟 App Store 讓使用者安裝 App 的用途。</p><p><strong>ius<br></strong>自訂的 URL Schema。</p><p><strong>ipfl<br></strong>當 App 尚未安裝時，在 iPads 上所開啟的連結，與 ifl 的用途類似，只是專門用在 iPads 上。</p><p><strong>ipbi<br></strong>在 iPads 上用來開啟連結的 App Bundle ID，該 App 必須要在 Firebase Console 的 Overview 頁面中與專案連結在一起。</p><p><strong>isi</strong><br>App 的 Apple Store ID，當使用者尚未安裝 App 時用來帶使用者到 App Store。</p><p><strong>imv<br></strong>能夠開啟連結的最低 App 版本，當 App 開啟時，這個 flag 會被傳到 App 中，App 中必須決定該如何處理。</p><h4>Other Parameters</h4><p>主要為除了 Android 和 iOS 平台外所使用的參數。</p><p><strong>ofl</strong><br>除了 Android 和 iOS 平台，其他平台所開啟的連結，這在電腦版中用來指定不同的行為非常好用，像是開啟 App 內容的滿版網頁，或是其他用來安裝 App 的 Dynamic Link Payload。</p><h4>Navigation Parameters</h4><p>Navigation 所使用的參數。</p><p><strong>efr</strong><br>將 <strong>efr</strong> 設為 1 開啟 Dynamic Link 時會略過 App 的預覽頁面，直接跳轉到商店或是 App；App 的預覽畫面在使用者開啟 Dynamic Link時，可以更可靠的將使用者帶到更適合的目的地</p><p>然而，如果你期望 Dynamic Link 只在 App 中被開啟，在沒有 App 的預覽畫面時會更可靠，因此也可以選擇將 <strong>efr</strong> 設為 0 來停止它。</p><blockquote>App 的預覽畫面現在只會在 iOS 上出現， Android 上之後可能也會出現，這個參數將會影響到 Dynamic Link 在兩個平台上的行為。</blockquote><h4>Social Meta Tag Parameters</h4><p>用在社群分享的參數。</p><p><strong>st</strong><br>用來當作分享在社群貼文中的標題。</p><p><strong>sd</strong><br>用來當作分享在社群貼文中的描述。</p><p><strong>si</strong><br>跟這個連結有相關的圖片，圖片至少要 300*200px 且小於 300KB。</p><h4>Analytics Parameters</h4><p>用於數據分析的參數。</p><p><strong>Google Play Analytics Parameters</strong></p><ul><li>utm_source</li><li>utm_medium</li><li>utm_campaign</li><li>utm_term</li><li>utm_content</li><li>gclid</li></ul><p><strong>iTunes Connect Analytics Parameters</strong></p><ul><li>at</li><li>ct</li><li>mt</li><li>pt</li></ul><h4>Debug Parameters</h4><p>用於偵錯的參數。</p><p><strong>d</strong><br>透過將 <strong>d</strong> 設為 1 進入偵錯模式，將會產生 Dynamic Link 的行為流程圖，而不是載入 Dynamic Link，可以用來預覽 Dynamic Link 在不同平台和配置的行為。</p><pre>https://link.swag.live?link=your_deep_link&amp;<strong>d=1</strong></pre><h3>總結</h3><p>使用 Firebase Dynamic Link 時，可以結合 Android 和 iOS 兩平台的參數，來產生在 Android 和 iOS 上都能夠使用的 Dynamic Link。</p><p>另外，當使用者尚未安裝 Mobile App 時，不是每次都需要導到商店，這種情況下 iOS Parameters 可以不帶 <strong>isi</strong>，這樣就能夠不跳到 App Store。</p><p><strong>ifl</strong>、<strong>ipfl</strong>、<strong>afl</strong> 和 <strong>ofl</strong> 在大多數的情況下可以不用帶，但如果有需要針對尚未安裝 Mobile App 的使用者有不同行為（像是：安裝 Mobile App 就能夠獲得一些特別促銷……等），在這種情況下就蠻適合透過這幾個參數，來設定這種不同的行為。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd8259974eec" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/firebase-dynamic-link-%E7%B0%A1%E5%96%AE%E4%BB%8B%E7%B4%B9-fd8259974eec">Firebase Dynamic Link — 簡單介紹</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tig- CLI 版的 Sourcetree]]></title>
            <link>https://medium.com/swag/tig-cli-%E7%89%88%E7%9A%84-sourcetree-3b375de95f58?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/3b375de95f58</guid>
            <category><![CDATA[tools]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[cli]]></category>
            <dc:creator><![CDATA[Roxy Chen]]></dc:creator>
            <pubDate>Wed, 03 Apr 2019 05:52:27 GMT</pubDate>
            <atom:updated>2019-04-03T05:52:27.474Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mf__I40H7VCwTRR-H1bHvQ.jpeg" /></figure><h3>什麼是 Tig</h3><p>Tig 是 Git browser 的一種<br>除了可以比原生 git 秀出更詳細的 log 之外他針對.git操作的方式也可以更多元<br>簡單說 Tig 就像 CLI版的Sourcetree，可以以視覺化方式在 Terminal 操作 Git 的工具。 雖然Git 本身已經就擁有 interactive mod 這些具便利性的指令，但是 Tig 這隻工具有提供更多方式讓你可以用更進階的方式去修改你的Git commit。</p><h3>安裝方式</h3><h4>macOS 透過 Homebrew 安裝</h4><pre>brew update<br>brew install tig</pre><h4>Ubuntu 透過 apt-get 安裝</h4><pre>sudo apt-get update<br>sudo apt-get install tig</pre><h3>基本操作</h3><p>在已經 git init 過的專案底下輸入 tig<br>就可以直接進入Tig 的main view <br>來查看當前git的commit<br>可以上下切換commit 後 enter 查看當前commit 的diff</p><h3>Diff View</h3><p>進入Diff View後 按 j/k 可上下捲動diff 檢視變更<br>按q則可離開diff view<br>回到main view 後 按t 則會進入tree view</p><h3>利用Tree view 來快速切換要檢視的Branch</h3><p>通常在git 底下是必需先checkout 的<br>但是在tig可以直接查看指定branch 底下的commit <br>不管在哪一個view底下都可以用大寫的S來 切換到status view 底下 並用u 加入指定的檔案到這次的commit 中</p><p>最後<br>如果專案沒有任何commit 紀錄 tig 是會報錯的</p><pre>tig: No revisions match the given arguments.</pre><p>這時候還是可以透過tig 來增加commit <br>只要直接執行tig status 然後再按 `u `來新增檔案即可</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b375de95f58" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/tig-cli-%E7%89%88%E7%9A%84-sourcetree-3b375de95f58">Tig- CLI 版的 Sourcetree</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[重構時順便還的技術債]]></title>
            <link>https://medium.com/swag/%E9%87%8D%E6%A7%8B%E6%99%82%E9%A0%86%E4%BE%BF%E9%82%84%E7%9A%84%E6%8A%80%E8%A1%93%E5%82%B5-7ffaa923a025?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/7ffaa923a025</guid>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Peter Chang]]></dc:creator>
            <pubDate>Wed, 20 Mar 2019 04:10:56 GMT</pubDate>
            <atom:updated>2019-03-18T14:32:14.162Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*twPe7S_WsW2fUyDBGob3MA.png" /></figure><p>一個iOS APP從2016至今已經快三年了，這段時間除了iOS系統本身的變動外，也多了新的語言Swift 加入，而且在這段期間內歷經各種大大小小因應營運的需求及急速功能變動，因此逐漸累積了不少技術債，為了面對未來更多的挑戰，償還歷史技術債是在所難免的，但是專案本身要進行大範圍重構是很困難的，畢竟產品已經在營運中是不等人的，因此我們的目標是縮限在有限的時間內達到有效的優化目的的重構，因此我們team根據目前的架構以及所遭遇的問題，綜合時間及範圍的掌握程度，選擇了幾個方向進行，其中一個就是APP中常用的user model。</p><h4><strong>問題評估</strong></h4><p>原本的程式大約如下:</p><pre><a href="http://twitter.com/interface">@interface</a> SWUserModel: MTLModel  &lt;MTLJSONSerializing&gt;<br><a href="http://twitter.com/property">@property</a> (copy ,nonatomic) NSString  *identifier;<br><a href="http://twitter.com/property">@property</a> (copy ,nonatomic) NSString  *username;<br>- (void)checkMembership:(void (^)(BOOL isMember))result;<br><a href="http://twitter.com/end">@end</a></pre><p>針對user model進行評估後，決定以下三個項目為目標<br> ⁃ Objective-C to Swift<br> ⁃ No Mantle <br> ⁃ No API call</p><p>首先是APP本身早期是用Objective-C開發的，但是目前ject已經逐漸轉以Swift 為主要開發語言，雖然可以用Swift extension方式擴展，但為了可以充分發揮新的語言帶來的好處及便利，因此想要藉此機會轉換過去。<br>而Mantle則是用來將JSON轉成object用的套件，但是改用Swift後就有decodable可以直接取代了。<br>再來就是在資料model中有進行呼叫API的行為，這雖然是一個很方便（偷懶）的方式，但是不符合SRP原則，因此也一直是我們想要下手處理的目標。</p><h4>解決方案</h4><p>考量不影響既有架構下抽換掉舊的data model的方式，而且也有可能新舊架構要並存一段時間的情形，決定採用protocol方式把data model實體藏起來，用統一的介面來溝通，這樣外界存取時就完全不需要管拿到的model實體是誰，之後就算拔掉整個舊model或者以後再更換model時會受到影響就會相當低。</p><p>因此先用Objective-C方式整理出介面，之後重構完成後就算用Swift方式重新寫一次也花不了多少功夫。</p><pre><a href="http://twitter.com/protocol">@protocol</a> UserProtocol &lt;NSObject&gt;<br><a href="http://twitter.com/property">@property</a> (copy ,nonatomic) NSString *identifier;<br><a href="http://twitter.com/property">@property</a> (copy ,nonatomic) NSString *username;<br><a href="http://twitter.com/end">@end</a></pre><p>因此新的Swift版本的User Model就會如下</p><pre><a href="http://twitter.com/objcMembers">@objcMembers</a> class UserModel:NSObject ,Codable ,UserProtocol {<br> var identifier: String<br> var username: String?</pre><pre>private enum CodingKeys:String ,CodingKey {<br>  case userID =&quot;id&quot;<br>  case username<br> }<br> func encode(with aCoder: NSCoder) {<br>     aCoder.encode(userID, forKey: &quot;identifier&quot;)<br>     aCoder.encode(username, forKey: &quot;username&quot;)<br> }<br> required init?(from decoder:Decoder )throws {<br>  let container = try decoder.container(keyedBy:CodingKeys.self)<br>  self.userID =try container.decodeIfPresent (String.self, forKey: .userID)<br>  self.username = try container.decodeIfPresent(String.self , forKey: .username) ?? &quot;&quot;<br> }<br>}</pre><p>然後找了個module將原本使用SWUserModel部分全部以UserProtocol取代後，進行小部分的測試並確認無誤後，但是這時突然發覺還有個重要問題還沒有解決，就是APP本身為了暫存目前使用者資料，所以將整個model object存下來在本地端，如果不處理這部分的話，會造成既有的使用者在升級到新版時，因為與新格式不符而變成需要重新進行登入，更甚者可能發生crash的狀況，因此還是必須有一個舊轉新的升級動作，以避免影響使用者體驗。</p><p>因此另外寫了個CurrentUserArchiver class，用來處理存取本地端資料時的介面，當新舊user model進行載入本地端使用者物件時，如果是用舊的model時會進行轉換成新的，並將轉換的行為包裝起來。</p><pre><a href="http://twitter.com/objcMembers">@objcMembers</a> class CurrentUserArchiver: NSObject, NSKeyedUnarchiverDelegate {<br>    static let shared = CurrentUserArchiver()<br>    private let userDefaults = CurrentUserDefaults()</pre><pre>func transfer(_ object: Any) -&gt; Data {<br>        return NSKeyedArchiver.archivedData(withRootObject: object)<br>    }<br>    <br>    func revert(from data: Data) -&gt; Any? {<br>        do {<br>            let obj = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)<br>            if let obj = obj as? SWCurrentUserModel {<br>                let user = CurrentUserModel.from(swCurrentUser: obj)<br>                return user<br>            }</pre><pre>return obj<br>        }<br>        catch let (err) {<br>            debugPrint(err)<br>        }<br>        return nil<br>    }<br>    <br>    func unarchived() -&gt; Any? {<br>        guard let archivedData = userDefaults.currentUser else { return nil }<br>        return revert(from: archivedData)<br>    }<br>    <br>    func archived(user: Any) {<br>        var object = user<br>        if let user = user as? SWCurrentUserModel {<br>            object = CurrentUserModel.from(swCurrentUser: user) as Any<br>        }<br>        <br>        userDefaults.currentUser = transfer(object)<br>    }<br>}</pre><p>這樣之後就可以無痛從舊的轉換成新的了。</p><p>至於呼叫API的處理，以根據使用的需求來分析，所以選擇的處理方式是用Singleton的class，由需要的View Controller來跟Singleton請求，這樣也可以避免掉重複的API request。</p><h4>結論</h4><p>雖然沒有一一帶到所有細節，到此基本上已經達到原本預期目標，其實在整個過程中還會需要一併納入考量的像是流程優化，去除多餘的、不必要處理等等。而花了那麼多功夫，其實也只是還了小部分技術債，但是可以讓產品本身開始往正確的方向發展。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7ffaa923a025" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/%E9%87%8D%E6%A7%8B%E6%99%82%E9%A0%86%E4%BE%BF%E9%82%84%E7%9A%84%E6%8A%80%E8%A1%93%E5%82%B5-7ffaa923a025">重構時順便還的技術債</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Side effect of messing up with Rx operators: concatMap() vs flatMap() in RxJava. Part I]]></title>
            <link>https://medium.com/swag/side-effect-of-messing-up-with-rx-operators-concatmap-vs-flatmap-in-rxjava-part-i-8436a419024f?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/8436a419024f</guid>
            <category><![CDATA[reactivex]]></category>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[rxjava]]></category>
            <dc:creator><![CDATA[WeiJung Yang]]></dc:creator>
            <pubDate>Mon, 11 Mar 2019 08:09:36 GMT</pubDate>
            <atom:updated>2019-03-11T08:09:36.065Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3UKnMBVDFRb-hIjwp3XceA.jpeg" /><figcaption>Photo by <a href="https://www.instagram.com/p/BtsAwqJHj63/?utm_source=ig_share_sheet&amp;igshid=1x7clal7p5ijl">J.H.YE</a></figcaption></figure><p>When the question comes to What is the difference between concatMap() and flatMap() in RxJava? People would tell you:</p><blockquote><strong>flatMap() doesn’t care about orders, while concatMap does.</strong></blockquote><p>And I bet a lot of people read it from</p><p><a href="https://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/">RxJava Observable tranformation: concatMap() vs flatMap() | Fernando Cejas</a></p><p>So do I. That explains why their signatures are the same, and why people always get confused about when to use which.</p><p>At first I was like <strong>“ I don’t really care, sometimes I use flatMap, sometimes I use concatMap.”. </strong>I never had a chance to have the problem that needs to take care of orders.</p><p>And like the author himself said: he didn’t took a look at the Rx document before he uses it. Neither did I. I don’t even understand what his code is to do. Until one day, a strange bug came to me. I finally realized that it is something to do with the concatMap/flatMap things.</p><p>Here is what I found.</p><p>TL;DR</p><h3>Make sure every observable completes if you use concatMap(), and it really matters</h3><p>Say I have 3 Observables that looks like below</p><pre>val source = Observable.create&lt;Int&gt;<strong>{<br>    it</strong>.onNext(1)<br>    <strong>it</strong>.onNext(2)<br>    <strong>it</strong>.onComplete()<br>    <strong>it</strong>.onNext(3)<br><strong>}</strong></pre><pre>fun operationCompletes(number: Int): Observable&lt;Int&gt; {<br>    return Observable.create&lt;Int&gt; <strong>{<br>        it</strong>.onNext(number)<br>        <strong>it</strong>.onComplete()<br>    <strong>}<br></strong>}</pre><pre>fun operationNoCompletes(number: Int): Observable&lt;Int&gt; {<br>    return Observable.create <strong>{<br>        it</strong>.onNext(number)<br>    <strong>}<br></strong>}</pre><p>The source emits onNext(1), onNext(2), onComplete() and then try to fire onNext(3). To make things easier, the functions do nothing but pass the variable to downstream. One is with onComplete() while the other one is not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1006/1*zZB469GmuIHMcFDMwhK7tQ.png" /></figure><p>The expected result looks like:</p><pre>I/System.out: [Original]OnNext: 1<br>I/System.out: [Original]OnNext: 2<br>I/System.out: [Original]Completed</pre><p>And then let’s try to connect observables with flatMap()</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/972/1*AuorAyj937p3VOBlw6Ut9w.png" /></figure><p>And the result is</p><pre>I/System.out: [FlatMapC]OnNext: 1<br>I/System.out: [FlatMapC]OnNext: 2<br>I/System.out: [FlatMapC]OnComplete<br>I/System.out: ######<br>I/System.out: [FlatMapNC]OnNext: 1<br>I/System.out: [FlatMapNC]OnNext: 2<br>I/System.out: ######</pre><p>Hmm… the <em>onComplete()</em> is gone after the non-complete observable. But so what? onNext() is enough for most of the time, and the non-complete observable itself choose not to fire onComplete(). That’s the desired behavior.</p><p>Okay, Let’s change nothing but flatMap() to concatMap()</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/966/1*hv5ddzNqXEFfW23qqosY9A.png" /></figure><p>The result is:</p><pre>I/System.out: [ConcatC]OnNext: 1<br>I/System.out: [ConcatC]OnNext: 2<br>I/System.out: [ConcatC]OnComplete<br>I/System.out: ######<br>I/System.out: [ConcatNC]OnNext 1<br>I/System.out: ######</pre><p>wait… what? Only onNext(1) was emitted in the last case.</p><p>This actually makes sense. concatMap() takes orders in consideration, so it must wait for a signal to tell him that he can do next.</p><p>I would like to go deeper with the source code and Rx documents, but it is more complicated than I thought. So the part I is just to share this common error that happens to Rx beginners. Part II will be wait until I found how to tell the story.</p><p>Any feedback is welcome. I would be happy to hear how programmers are to avoid this things from happening in a team work project.</p><h3>References</h3><p><a href="http://reactivex.io/documentation/operators/flatmap.html">flatMap()</a></p><p><a href="https://fernandocejas.com/2015/01/11/rxjava-observable-tranformation-concatmap-vs-flatmap/">RxJava Observable tranformation: concatMap() vs flatMap()</a></p><p><a href="https://stackoverflow.com/questions/24571491/what-is-the-difference-between-concatmap-and-flatmap-in-rxjava">discussions on stackoverflows</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8436a419024f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/side-effect-of-messing-up-with-rx-operators-concatmap-vs-flatmap-in-rxjava-part-i-8436a419024f">Side effect of messing up with Rx operators: concatMap() vs flatMap() in RxJava. Part I</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[品質你我他]]></title>
            <link>https://medium.com/swag/%E5%93%81%E8%B3%AA%E4%BD%A0%E6%88%91%E4%BB%96-394b1c224c6e?source=rss----bfbe2f925f7e---4</link>
            <guid isPermaLink="false">https://medium.com/p/394b1c224c6e</guid>
            <dc:creator><![CDATA[Angela Huang]]></dc:creator>
            <pubDate>Fri, 15 Feb 2019 12:36:14 GMT</pubDate>
            <atom:updated>2019-02-15T12:36:14.135Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/382/1*UuopmGwjeXCki4P3v_rfKA.jpeg" /></figure><h4>App 品質的重要性</h4><p>App 的品質代表著，是會被留在裝置上，還是會被立馬移除<br>App 的品質代表著，是會被好康道相報，還是會被壞事傳千里<br>App 的品質代表著每次推出新版本時，是會心驚膽跳，還是會悠閒的喝著咖啡看著業績數字的成長</p><h4>App 的品質來自於團隊中的每一位成員</h4><p>品質，不是靠最後的查 bug、修 bug，來確保是否達品質要求。<br>品質，是早從確認需求、規畫設計就得開始管理的。<br>如同蓋社區大廈一般，若建築師未能將設計圖畫好、未能確認好各區所需用到建材及數量時，即讓工人進行施工，而在施工過程中，又未能將設計圖補足，這麼一來，難保最後不會蓋出漏水滲水甚至是大地震來時會倒塌的房子。</p><p>而當需求被明確化被 spec 化了，這時就是輪到將 spec 實現化的執行單位要管理執行過程中的品質並符合其需求。<br>如同建築師已將設計圖畫好，但工人在施工過程中，卻沒去注意到一戶要隔成兩房一廳或三房兩廳，難保不會蓋出不符合需求的房子。</p><p>或許有些人會認為，品質的檢測是只需放在最後最後的檢查，但我想帶個想法給大家，〔品質的檢測，應該是在專案的每一個階段〕。<br>如同工人挖好地基了，工人&amp;建築師&amp;檢驗人員都應確認其地基挖的夠深夠寬後，房子才開始往地面向上蓋，若在此時及時發現地基挖的不夠就能及時補強。</p><p>倘若其專案不只有需求單位，另還有使用者時，就不光只是檢測是否符合需求，連同提供給使用者觀看內容的審核也是品質的一環。</p><p>如同社區大樓新建案完成了，若能有間宅裝過的樣品屋，更能提高這一片空屋的品質，進而較獲得看屋者的青睞。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QYoI_T362YSFJejkHACx3Q.jpeg" /></figure><p>而品質檢測的同時，最需要的是時間。專案在時間緊迫情況下，儘管看起來似乎完成了檢測，但日後也許很可能那些未檢測到的項目，陸續出現問題。<br>很多時候，特別是在追趕進度時候，最常被壓縮的就是測試時間，這也同時代表著 App 的品質被犠牲了。</p><p>品質意識不是單一層面不是一個單位的責任，品質是多面化是團隊每一位成員出自內心對品質的認知。</p><h4>QA 在團隊中所扮演的角色</h4><p>QA，有些人會把它當成檢查單位、有些人會把它當成審核單位，但我把它定義為協助單位，協助團隊在專案開發前，即能思考到複雜及反向的情況，而不再讓團隊處於僅思考到單向好的方面；協助團隊在專案開發過程中，陸續提供測試結果給 PM 甚至是需求單位，以確保驗收標準及專案成果等同於需求單位所要的；協助團隊在專案開發後，確保其專案的穩定性並提高專案的品質。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=394b1c224c6e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swag/%E5%93%81%E8%B3%AA%E4%BD%A0%E6%88%91%E4%BB%96-394b1c224c6e">品質你我他</a> was originally published in <a href="https://medium.com/swag">SWAG</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>