【Swift】AWS S3にあるm3u8を再生したい。【iOS】 - 1日目

AWSとか諸々の知識が無さすぎて意味不明なところで悩んで脱線して何やってたかわからなくなることが多々あるので、作業しながら記録をつけていく。

数日後追記:この日記には何の解決法ものっていません。

Agoraを使ってLiveをするアプリを作っていて、そのLiveをアーカイブで残したいとなった。アーカイブは以下のコードでできた。だがしかし、AWSに行ってアーカイブされたm3u8ファイルを開こうとしたけどできない!


class MyCloudRecording {
   
   let username = "<agora username>"
   let password = "<agora pass>"
   let headers: HTTPHeaders = [
       "Content-Type": "application/json;charset=utf-8"
   ]
   let mode = "individual"
   var channelName = ""
   var uid = ""
   var resourceId = ""
   var sid = ""
   
   let token: () -> String
   
   init(token: @escaping () -> String) {
       self.token = token
   }
   
   func getResourceId(channelName: String) {
       self.channelName = channelName
       self.uid = "\(randomInt())"
       
       let body : [String : Any] = [
               "cname": channelName,
               "uid": uid,
               "clientRequest": [
                   "region": "AP",
                   "resourceExpiredHour": 24
               ]
       ]
       AF.request("https://api.agora.io/v1/apps/\(KeyCenter.AppId)/cloud_recording/acquire",
                     method: .post,
                     parameters: body,
                     encoding: JSONEncoding.default,
                       headers: headers)
           .authenticate(username: username, password: password)
           .responseDecodable(of: AquiredResp.self, completionHandler: { response in
               print("cloud_recording.acquire")
               switch response.result {
               case .success(let data):
                   print("resourceId:\(data.resourceId)")
                   self.resourceId = data.resourceId
                   self.startRecording()
               case .failure(let error):
                   print("error:\(error)")
               }
       })
   }
   func startRecording() {
       //https://docs.agora.io/en/cloud-recording/cloud_recording_api_start?platform=RESTful
       let body : [String : Any] = [
           "uid": uid,
           "cname": channelName,
           "clientRequest": [
               "token": token(),
               "recordingConfig": [
                   "maxIdleTime": 30,
                   "streamMode": "default",
                   "streamTypes": 2,
                   "channelType": 0,
                   /*"transcodingConfig": [
                       "height": 640,
                       "width": 360,
                       "bitrate": 500,
                       "fps": 15,
                       "mixedVideoLayout": 1,
                       "backgroundColor": "#FF0000"
                   ],*/
                   /*"subscribeVideoUids": [
                       "123",
                       "456"
                   ],
                   "subscribeAudioUids": [
                       "123",
                       "456"
                   ],*/
                   "subscribeUidGroup": 1// 3 to 7 ppls
               ],
               "recordingFileConfig": [
                   "avFileType": [
                       "hls"
                   ]
               ],
               "storageConfig": [
                   "secretKey": "<secretKey>",
                   "vendor": 1,
                   "bucket": "<bucketName>",
                   "accessKey": "<accessKey>",
                   "region": 10,
                   "fileNamePrefix": [
                       channelName
                   ],
                   "extensionParams": [
                       //"tag":"xxxxxx",
                       "sse":"kms"
                  ]
               ]
           ]
       ]
       AF.request("https://api.agora.io/v1/apps/\(KeyCenter.AppId)/cloud_recording/resourceid/\(resourceId)/mode/\(mode)/start",
                     method: .post,
                     parameters: body,
                     encoding: JSONEncoding.default,
                       headers: headers)
           .authenticate(username: username, password: password)
           /*.responseString { response in
               switch response.result {
               case .success(let data):
                   print("cloud_recording.start.response:\(data)")
               case .failure(let error):
                   print("error:\(error)")
               }
           }*/
           .responseDecodable(of: CloudRecordingStartedResp.self, completionHandler: { response in
               print("cloud_recording.start")
               switch response.result {
               case .success(let data):
                   print("resourceId:\(data.resourceId)\nsid:\(data.sid)")
                   self.sid = data.sid
               case .failure(let error):
                   print("error:\(error)")
               }
       })
   }
   func stopRecording() {
       let body : [String : Any] = [
           "cname": channelName,
           "uid": uid,
           "clientRequest": [
               "async_stop": true
           ]
       ]
       print("stopRecording.resourceId", resourceId, "\nstopRecording.sid", sid, "\nstopRecording.node", mode)
       let url = "https://api.agora.io/v1/apps/\(KeyCenter.AppId)/cloud_recording/resourceid/\(resourceId)/sid/\(sid)/mode/\(mode)/stop"
       AF.request(url,
                 method: .post,
                 parameters: body,
                 encoding: JSONEncoding.default,
                  headers: headers)
           .authenticate(username: username, password: password)
           .responseString { response in
               switch response.result {
               case .success(let data):
                   print("response:\(data)")
               case .failure(let error):
                   print("error:\(error)")
               }
           }
           /*.responseDecodable(of: CloudRecordingStartedResp.self, completionHandler: { response in
               print("cloud_recording.stop \(response)")
               switch response.result {
               case .success(let data):
                   print("resourceId:\(data.resourceId)\n\(data.sid)")
               case .failure(let error):
                   print("error:\(error)")
               }
       })*/
   }
   func randomInt() -> Int {
       let range = [0,1,2,3,4,5,6,7,8,9]
       var int = 0
       var keta = 1
       for _ in 0..<8 {
           int += (range.randomElement() ?? 1)*keta
           keta *= 10
       }
       print("randomInt", int)
       return int
   }
}

struct AquiredResp: Codable {
   var resourceId = ""
}
struct CloudRecordingStartedResp: Codable {
   var resourceId = ""
   var sid = ""
}

↓ アップロードされたファイルたち

スクリーンショット 2022-03-17 14.58.36

↓ ファイルをタップ

スクリーンショット 2022-03-17 14.58.48

↓ オブジェクト URLをタップすると…

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>H5594G0E7RJ8D1QX</RequestId>
<HostId>
OHdj7dRxAWuYp0DT2expJ+YNHBrfBa/TEf9U45bofP7+Ite21vDxFZdgCSIejOgAjvH1Wo0CNgI=
</HostId>
</Error>

エラーをコピペして調べてみたけど、どの情報も古くて役に立たない。

↓ ここに書いてあるのを試してみた。

『cloudfrontの「General」設定内に「Default Root Object」設定があります。』

って書いてあるけど、Generalってどこやねん。AWSページ内の検索バーで「CloudFront」と検索したらDistributionを作成するような画面が出てきたのでとりあえず作ってみた。

スクリーンショット 2022-03-17 15.13.42

「Default Root Object」の変更の仕方はここに書いてあった。

わけもわからず「Default Root Object」にindex.htmlと入力して変更を保存した。

再度さっきのm3u8のオブジェクト URLを開こうとすると、

This XML file does not appear to have any style information associated with it. The document tree is shown below....

ダメじゃん。

index.htmlを作成しないといけないのか?Webサイトじゃないから、そんなファイルはないのだが。

うわーん。

色々調べてみたけど何もでてこなかったので、諦めてSDKを試みる。​

Cocoapodに色々インストールして、

pod 'AWSMobileClient'
 pod 'AWSPinpoint'
 pod 'AWSS3'

以下を走らせてみたら​

func download() {
       let credentialProvider = AWSStaticCredentialsProvider(accessKey: MyAWS.accessKey, secretKey: MyAWS.secretKey)
       let configuration = AWSServiceConfiguration(region: .APEast1, credentialsProvider: credentialProvider)
       AWSServiceManager.default().defaultServiceConfiguration = configuration

       let expression = AWSS3TransferUtilityDownloadExpression()
       expression.progressBlock = {(task, progress) in DispatchQueue.main.async(execute: {
           // Do something e.g. Update a progress bar.
           print("File Progress:",Float(progress.fractionCompleted))

           let progress = Float(progress.fractionCompleted)
           print("progress", progress)
           
           })
       }

       var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?
       completionHandler = { (task, URL, data, error) -> Void in
           print("completionHandler.task", task, URL, data, error)
       }
       let transferUtility = AWSS3TransferUtility.default()

       transferUtility.downloadData(
           fromBucket: MyAWS.buketName,
           key: "<下の画像の場所のKeyを指定した>",
           expression: expression,
           completionHandler: completionHandler
           ).continueWith {
               (task) -> AnyObject? in if let error = task.error {
                   print("Error: \(error.localizedDescription)")
               }
               print("task.result", task.result)
               
               return nil;
       }
   }

スクリーンショット 2022-03-17 16.18.01

<head><title>503 Service Temporarily Unavailable</title></head>

うぉぉぉぉぉい。

調べてみた。

「オリジンサーバー」も「リクエスト率」も何なのかわからない。。!半泣きでバケットの中を隅々まで見てみる。

まだまだ時間かかりそうなのでタイムスタンプを押していく。

16:20

AWS設定項目が多すぎて発狂しそう。

とりあえずホスティングをやってみよう(迷走)。

16:41

ホスティングできた。

http://cloudrecordingtchers.s3-website-ap-northeast-1.amazonaws.com

でもAWSS3TransferUtilityDownloadExpressionの結果は同じ

<center><h1>503 Service Temporarily Unavailable</h1></center>

試しにS3から直接m3u8を開こうとすると。。。

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>InvalidArgument</Code>
<Message>
Requests specifying Server Side Encryption with AWS KMS managed keys require AWS Signature Version 4.
</Message>
<ArgumentName>Authorization</ArgumentName>
<ArgumentValue>null</ArgumentValue>
<RequestId>Y1EY7BNA20TB668K</RequestId>
<HostId>
AaeEXtDvqOD3L2Oa5+dnWNs5qw88dNiG1xnlqd20h0VcZgOQeCPgpQ188VOrRa3/ad8UKU/+1EA=
</HostId>
</Error>

なんか変わってる!!!

Requests specifying Server Side Encryption with AWS KMS managed keys req...エラーについて調べても何も出てこなかったので、改めてService Temporarily Unavailableについて調べる。

AWSの説明ページはただただ長い文章が書いてあって意識が飛びそうになる。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/http-503-service-unavailable.html

CloudFrontのPrice Classも「Use all edge locations」になってるから、エッジロケーションの問題ではないはず。

可能性1:Amazon S3 をオリジンサーバーとして使用している場合は、キー命名規則のベストプラクティスに従って Amazon S3 のパフォーマンスを最適化します。詳細については、Amazon Simple Storage Service ユーザーガイドの「Amazon S3 のパフォーマンスの最適化」を参照してください。

多分s3をオリジンサーバーとして使ってるからこれかな?「Amazon S3 のパフォーマンスの最適化」に飛ぶ。ただの長文が出てきた。

何をすればいいのか簡潔に教えてくれ!!!!(半ギレ)

長文読みたくないので、Macの文章読み上げをしてみたら日本語の音声で英語を読み始めてブチギレ。どうやって止めるのかわからない。

読み上げをBGMに、「Amazon S3 のパフォーマンスの最適化」について調べる。

17:16

何も出てこない。

というか、「Service Temporarily Unavailable」エラーの原因に「CloudFront は、受信されるリクエストをオリジンサーバーが処理しきれなくなったときに、このエラーを生成します。」って書いてあるけど、リクエスト1つしか送ってないはずなのにおかしくない?と今更気づく。

というか、CloudFrontどこで使ってるの?S3使ってると思ってたんだが。

拉致が開かないので stackoverflowで聞いてみた。

数日後の9:00

stackoverflowにも誰も返答してくれないので、AWS+Swiftの基本からやってみることにした。

...

なんかすごいわかりにくい。

何ステップもあるんだけど、ページを行ったり戻ったりする。段落もわかりずらいし、次の大ステップの中ステップ1で「まず、前の大ステップを完了させてください。」とか言われて発狂(脳弱)。終わらせてるって!!

9:30

よーし終わった!ビルドしてみよう!

Xcode : Build Failed(エラー表示無し)

😦

CocoapodにもPackageにもAWSのSDKが入ってるからかな。

Cocoapodの方のAWSをコメントアウトして、pod install。

Cocoapod : [!] Oh no, an error occurred.

😦

以下をやってみる。

rm Podfile.lock
pod deintegrate
pod install

Cocoapod : [!] Oh no, an error occurred.

😭

左上⚠️ボタンをタップすると「Update to Recommended settings」みたいなのが表示されてたので実行。→ビルド

Xcode : 'AgoraRtcKit/AgoraRtcEngineKit.h' file not found

さっきCocoapodを解体したから、必要なパッケージが無いようだ。

またpod installを試す。

Cocoapod : [!] Oh no, an error occurred.

😭

Podsフォルダーも消して、pod install。

Cocoapod : [!] Oh no, an error occurred.

😭

エラーメッセージは以下(Search for existing GitHub issues similar to yours:下のURLをコピペして飛んだgithubページの左上の検索バーのテキストをコピー)

Expected target or target_proxy, from which to fetch a uuid for target ''.Find and clear the PBXTargetDependency entry with uuid in your .xcodeproj.

clear the PBXTargetDependency entry すればいいのか。

検索検索...

9:53

何も出てこない

発狂しそうなのでお粥タイム。

10:03

Build PhasesのDependencies見てみたら、AWSのSDKが全てnullになってたので消去 → pod install

Cocoapod : Pod installation complete! There are 14 dependencies from the Podfile and 38 total pods installed.

できたーーーーーーーーーーーー🎉🎉🎉🎉🎉🎉

Build PhasesのDependenciesにAWSのSDK入れ直して。ビルド。

Xcode : No such module 'AWSCore'

🤗 < なんでや

とりあえずimport AWSCoreとその関連コードをコメントアウトしてXcode Build。

Xcode : Command CompileSwift failed with a nonzero exit code

はいはい。よくあるやつ。Clean Build Folder→Xcode再起動して再ビルド。

瀬戸弘司Youtubeを見ながら待つ。Xcodeのビルドに苦節1時間。AWSからかなり脱線してるなぁ。

ビルドに20分以上かかり、Build Failed(エラー表示無し)🙃

左上メニューの📄ボタンをタップすると、

何個かのAWSのSDKに「Command CompileSwiftSources failed with a nonzero exit code」というエラーを発見。

Derived Dataを消去してビルドしてみる。

また20分待って。。。

Build Failed エラー:No such module 'Alamofire'

podでAlamofire消してた。追加し直してpod install。

Cocoapod : [!] Oh no, an error occurred.

😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭

ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああぁぁ。。

色々試して、Dependenciesを再度全部消去してpod install。

できた。

またDependencies追加して、ビルド。

Xcode : 
全ての◯◯.swiftファイルに対して、Missing required modules: 'AwsCAuth', 'AwsCCal', 'AwsCCommon', 'AwsCHttp', 'AwsCIo', 'AwsCMqtt', 'AwsCSdkUtils'

aaa....

AWSのパッケージを全部追加して再度ビルドしてみる。

11:01

iPad mini hoshiinaa...

10分後おなじみbuild failed.

今度は一部の必要ないDependenciesでエラーが出てるようなので、消去。

ビルドすると今度は以下のエラー

missing required modules: 'AwsCAuth', 'AwsCCal', 'AwsCCommon', 'AwsCHttp', 'AwsCIo', 'AwsCMqtt', 'AwsCSdkUtils'

探しても同じようなエラーは見つからなかったのでstackoverflowで質問。

https://stackoverflow.com/questions/71566320/missing-required-modules-awscauth-awsccal-awsccommon-awschttp-awsc

11:40

解答はこないだろうと思うので、色々試してみる。

12:12

Xcode再起動。そしてビルドすると、コードの無い部分でエラーを指摘している。どうしちまったんだXcode...

もう一回ビルド

Xcode : missing required modules: 'AwsCAuth', 'AwsCCal', 'AwsCCommon', 'AwsCHttp', 'AwsCIo', 'AwsCMqtt', 'AwsCSdkUtils'

あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ

aaaaaaaaaaaaa

12:18

もういいわ。明日やります。


この記事が気に入ったらサポートをしてみませんか?