<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Demo on Joe's Blog</title><link>https://shinrenpan.github.io/tags/demo/</link><description>Recent content in Demo on Joe's Blog</description><generator>Hugo</generator><language>zh-tw</language><copyright>© Shinren Pan. All rights reserved.</copyright><lastBuildDate>Sun, 14 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://shinrenpan.github.io/tags/demo/index.xml" rel="self" type="application/rss+xml"/><item><title>FHIR OAuth Demo</title><link>https://shinrenpan.github.io/2025-03-18/</link><pubDate>Tue, 18 Mar 2025 00:00:00 +0000</pubDate><guid>https://shinrenpan.github.io/2025-03-18/</guid><description>&lt;p&gt;醫療系統的 App 要存取病患資料，繞不開 FHIR。這篇記錄如何用 SMART on FHIR 完成 OAuth 授權，並用 Apple 官方的 FHIRModels 套件解析回傳的資料。&lt;/p&gt;
&lt;p&gt;
 &lt;img src="https://shinrenpan.github.io/images/2025-03-18/01.gif" alt=""&gt;

&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="什麼是-smart-on-fhir"&gt;什麼是 SMART on FHIR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FHIR&lt;/strong&gt;（Fast Healthcare Interoperability Resources）：HL7 制定的醫療資料交換標準，R4 是目前主流版本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SMART on FHIR&lt;/strong&gt;：在 FHIR 上套用 OAuth 2.0 的授權框架，讓第三方 App 可以安全存取醫療系統的資料&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;流程跟一般的 OAuth Authorization Code Flow 一樣：取得 code → 換 token → 用 token 呼叫 API。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="整體流程"&gt;整體流程&lt;/h2&gt;
&lt;pre class="mermaid"&gt;sequenceDiagram
 participant App
 participant Browser as ASWebAuthenticationSession
 participant Server as SMART Server

 App-&amp;gt;&amp;gt;Browser: 開啟授權 URL
 Browser-&amp;gt;&amp;gt;Server: 使用者登入授權
 Server--&amp;gt;&amp;gt;Browser: redirect_uri?code=xxx
 Browser--&amp;gt;&amp;gt;App: callback URL
 App-&amp;gt;&amp;gt;Server: POST /token（帶 code）
 Server--&amp;gt;&amp;gt;App: access_token
 App-&amp;gt;&amp;gt;Server: GET /fhir/Patient（帶 Bearer token）
 Server--&amp;gt;&amp;gt;App: FHIR Bundle（病患資料）
 &lt;/pre&gt;

&lt;hr&gt;
&lt;h2 id="step-1建立授權-url"&gt;Step 1：建立授權 URL&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;func&lt;/span&gt; &lt;span style="color:#6639ba"&gt;makeOAuthURL&lt;/span&gt;&lt;span style="color:#1f2328"&gt;()&lt;/span&gt; &lt;span style="color:#1f2328"&gt;-&amp;gt;&lt;/span&gt; URL&lt;span style="color:#1f2328"&gt;?&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;let&lt;/span&gt; &lt;span style="color:#953800"&gt;authURI&lt;/span&gt; &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;https://launch.smarthealthit.org/.../auth/authorize&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;guard&lt;/span&gt; &lt;span style="color:#cf222e"&gt;var&lt;/span&gt; &lt;span style="color:#953800"&gt;components&lt;/span&gt; &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; URLComponents&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;string&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; authURI&lt;span style="color:#1f2328"&gt;)&lt;/span&gt; &lt;span style="color:#cf222e"&gt;else&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt; &lt;span style="color:#cf222e"&gt;return&lt;/span&gt; &lt;span style="color:#cf222e"&gt;nil&lt;/span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; components&lt;span style="color:#1f2328"&gt;.&lt;/span&gt;queryItems &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;.&lt;/span&gt;&lt;span style="color:#cf222e"&gt;init&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;name&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;response_type&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; value&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;.&lt;/span&gt;&lt;span style="color:#cf222e"&gt;init&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;name&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;redirect_uri&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; value&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;app://&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;.&lt;/span&gt;&lt;span style="color:#cf222e"&gt;init&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;name&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;aud&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; value&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;https://launch.smarthealthit.org/.../fhir&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;.&lt;/span&gt;&lt;span style="color:#cf222e"&gt;init&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;name&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; value&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;patient/*.cruds&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;return&lt;/span&gt; components&lt;span style="color:#1f2328"&gt;.&lt;/span&gt;url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;scope: patient/*.cruds&lt;/code&gt; 代表要求對所有 Patient 資源的讀寫權限。&lt;code&gt;redirect_uri&lt;/code&gt; 用自訂 scheme &lt;code&gt;app://&lt;/code&gt;，授權完成後系統會把 callback URL 傳回 App。&lt;/p&gt;</description></item><item><title>MQTT 實作聊天 Demo</title><link>https://shinrenpan.github.io/2025-03-04/</link><pubDate>Tue, 04 Mar 2025 00:00:00 +0000</pubDate><guid>https://shinrenpan.github.io/2025-03-04/</guid><description>&lt;p&gt;MQTT 是一個輕量的 Pub/Sub 協定，拿來做聊天有幾個優點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QoS（Quality of Service）內建&lt;/strong&gt;：提供三個等級的送達保證，最高等級 QoS 2 確保訊息恰好送達一次，不需要自己實作重送機制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payload 完全客製化&lt;/strong&gt;：IM 需要的語意——已讀、上線狀態、群組通知——全部定義在 payload 裡，彈性高&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Facebook Messenger 早期也是用 MQTT 做即時訊息的傳輸層。&lt;/p&gt;
&lt;p&gt;
 &lt;img src="https://shinrenpan.github.io/images/2025-03-05/01.gif" alt=""&gt;

&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="架構概覽"&gt;架構概覽&lt;/h2&gt;
&lt;p&gt;採用 MVVMC，兩個頁面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Home&lt;/strong&gt;：輸入使用者名稱進入聊天室&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Room&lt;/strong&gt;：聊天室主頁面，連線 MQTT broker、收發訊息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MQTT 的操作集中在 &lt;code&gt;MQTTManager&lt;/code&gt;，ViewModel 不直接碰 MQTT。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mqttmanager"&gt;MQTTManager&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;actor MQTTManager &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;static&lt;/span&gt; &lt;span style="color:#cf222e"&gt;let&lt;/span&gt; &lt;span style="color:#953800"&gt;shared&lt;/span&gt; &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; MQTTManager&lt;span style="color:#1f2328"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;private&lt;/span&gt; &lt;span style="color:#cf222e"&gt;let&lt;/span&gt; &lt;span style="color:#953800"&gt;topic&lt;/span&gt; &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;JoeChatDemo/chat&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;private&lt;/span&gt; &lt;span style="color:#cf222e"&gt;var&lt;/span&gt; &lt;span style="color:#953800"&gt;client&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; MQTTClient&lt;span style="color:#1f2328"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;private&lt;/span&gt; &lt;span style="color:#cf222e"&gt;let&lt;/span&gt; &lt;span style="color:#953800"&gt;eventLoopGroup&lt;/span&gt; &lt;span style="color:#1f2328"&gt;=&lt;/span&gt; NIOTSEventLoopGroup&lt;span style="color:#1f2328"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;用 &lt;code&gt;actor&lt;/code&gt; 封裝，確保 &lt;code&gt;client&lt;/code&gt; 的狀態在 concurrent 環境下安全存取。&lt;/p&gt;</description></item><item><title>播放本地端 m3u8</title><link>https://shinrenpan.github.io/2022-07-13/</link><pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate><guid>https://shinrenpan.github.io/2022-07-13/</guid><description>&lt;p&gt;AVPlayer 可以直接以 &lt;code&gt;file://&lt;/code&gt; URL 播放本地影片，但對於 &lt;code&gt;.m3u8&lt;/code&gt; playlist，它會在解析完 manifest 後，&lt;strong&gt;直接嘗試以原始路徑載入每一個 segment&lt;/strong&gt;。這個過程完全繞過了 &lt;code&gt;AVAssetResourceLoaderDelegate&lt;/code&gt;，導致無法攔截並自訂載入行為。&lt;/p&gt;
&lt;p&gt;簡單說：&lt;code&gt;file://&lt;/code&gt; URL 太「正常」了，AVPlayer 自己就處理掉，不給你插手的機會。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="解法自訂-scheme"&gt;解法：自訂 Scheme&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AVAssetResourceLoaderDelegate&lt;/code&gt; 只對 &lt;strong&gt;AVPlayer 不認識的 scheme&lt;/strong&gt; 生效。&lt;/p&gt;
&lt;p&gt;做法是在建立 &lt;code&gt;AVURLAsset&lt;/code&gt; 時，把 &lt;code&gt;file://&lt;/code&gt; 替換成自訂的 &lt;code&gt;local://&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;file:///var/mobile/.../video.m3u8
 ↓ 替換 scheme
local:///var/mobile/.../video.m3u8
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;AVPlayer 看到 &lt;code&gt;local://&lt;/code&gt;，不知道怎麼處理，就把請求轉交給 &lt;code&gt;AVAssetResourceLoaderDelegate&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;關鍵 delegate 方法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;func&lt;/span&gt; &lt;span style="color:#6639ba"&gt;resourceLoader&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;&lt;span style="color:#cf222e"&gt;_&lt;/span&gt; resourceLoader&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; AVAssetResourceLoader&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; shouldWaitForRenewalOfRequestedResource renewalRequest&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; AVAssetResourceRenewalRequest&lt;span style="color:#1f2328"&gt;)&lt;/span&gt; &lt;span style="color:#1f2328"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#6639ba"&gt;Bool&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;func&lt;/span&gt; &lt;span style="color:#6639ba"&gt;resourceLoader&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;&lt;span style="color:#cf222e"&gt;_&lt;/span&gt; resourceLoader&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; AVAssetResourceLoader&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; shouldWaitForLoadingOfRequestedResource loadingRequest&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; AVAssetResourceLoadingRequest&lt;span style="color:#1f2328"&gt;)&lt;/span&gt; &lt;span style="color:#1f2328"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#6639ba"&gt;Bool&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;&lt;a href="https://github.com/shinrenpan/play-local-m3u8"&gt;Demo&lt;/a&gt; ｜ &lt;a href="https://stackoverflow.com/questions/45670774/playing-offline-hls-with-aes-128-encryption-ios/45957045#45957045"&gt;參考&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;本文使用 Claude 共同完成&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>