肇鑫的技术博客

业精于勤,荒于嬉

AVMovie始终显示视频时长为0问题的分析、解决

推特仅支持0.5秒到140秒之内长度的视频,因此上传前要先检验视频的长度。测试时,我发现,Finder分享的视频时长始终为0。

我查询了一下,最初看到的说法是,AVAssetAVMovie的父类)的加载是异步的,不能够立即获得时长,而需要用键值观察的方法,注册通知来观察duration这个属性何时变化。昨晚看到这里,我就睡了。

今天再看,发现有协议AVAsynchronousKeyValueLoading,可以用来异步处理键值。方法loadValuesAsynchronously(forKeys:completionHandler:)会在加载完成后执行completionHandler。这样就不用我们自己手动注册键值观察的通知了。

但是,我测试这个方法不好用。Finder传来的视频,无论是否使用这个异步方法,时长都始终为0。

我不用Finder,采用照片应用进行调试,发现无论是不是用这个异步方法,都能获得正确的时长。

结合上一篇Finder分享视频到共享扩展的一些限制的小结。我产生了新的想法,经过测试,我的猜想是正确的。

那就是,AVMovie在读取URL的时候,会正确获得时长,如果是Data的话,则不可以。想测试也很简单,把URL变成Data,再创建AVMovie;或者把Data写入到URL,再创建AVMovie。就可以证实了。

我不清楚这个是苹果API的错误,还是特性。但是这个至少是没有注明的。

Finder分享视频到共享扩展的一些限制的小结

通常,我们使用NSItemProviderloadItem(forTypeIdentifier:options:completionHandler:)方法来加载对应类型的对象。但是Finder提供的信息有问题。不能使用推荐的类型来获得想要的对象。

比如分享mp4文件时,Finder提供的NSItemProvider是这样的。

<NSItemProvider: 0x6000000bfa40> {types = (
    "public.file-url",
    "public.url",
    "public.mpeg-4"
)}

你用"public.mpeg-4"作为类型来获取数据,不能获得视频文件对应的URL。这是很罕见的。因为大部分情况下,比如通过照片应用来获得视频时,都可以通过这种方式获得URL。

Finder提供给"public.mpeg-4"实际上是一个Data。但是这个Data用在这里实际上是有问题的。苹果不应该这么做。为什么呢?因为相比于其它类型的文件,比如图片或者声音之类的,视频文件可以是很大的。如果你要使用这个视频文件,那么通过Data来加载,就会占据大量的内存。实际上,苹果自己就在加载视频的文档中写到,不要全部加载到内存。

apple data warning

因为存在这个限制,Finder实际上传来的Data可能是不完整的。比如我尝试分享一个4GB的视频时,实际获得的Data的大小是415MB,并不完整。

结论,既然Data既占内存,又可能不完整,为什么Finder还要提供呢?只能说,苹果做错了。

临时的处理办法是这样的,先用"public.mpeg-4"尝试获取URL,如果获取失败。那么就看NSItemProvider是否支持URL。如果支持就再用"public.url"尝试获取URL。如果还不行,那就认为是没法处理。为什么不反过来呢?因为大部分的情况,都是第一种方式就能直接处理。Finder这么做实际上是错误的。但是为了适应它,才需要调整我们的代码。

Twitter首次授权,三步这么走

three steps

初步结论

发送部分

  • methodPOST时,postContent为nil或需要上传的数据。
    • 例外:申请用户授权的第一步时,oauth_callback是作为header的一部分,而不是postContent
  • methodGET时,会需要在baseURL后连接GET的信息urlGetParameter,形成新的fullURL
  • oauth_signature的计算,是将methodbaseURL和所有要发送的信息连起来,并计算校验值。其中,除methodbaseURL之外的所有的键值对,需要按照键的字母顺序进行排列。
  • oauth_signature的计算,signing_key由两部分构成,consumer_secretoauth_token_secret两部分合成,中间用&连接。
    • 例外:申请用户授权的第一步时,由于没有下发的oauth_token_secretsigning_keyconsumer_secret+&
  • header永远使用Authorization字段,该字段使用的键为七个:oauth_consumer_keyoauth_nonceoauth_signatureoauth_signature_methodoauth_timestampoauth_tokenoauth_version
    • 例外:申请用户授权的第一步时,此时还没有服务器下发的oauth_token。但这时要把oauth_callback加入其中。
  • 例外:虽然oauth_signature的计算方法写的是使用baseURL,但是我发现,当HttpMethodGET时,既可以使用baseURL进行计算,也可以使用fullURL进行计算。当使用fullURL时,需要把GET的参数项对应的键值对,从oauth_signature的计算中移除。

接收部分

  • 接收成功的标志是successStatusCode=200。
  • 接收的内容是一组由预定的键组成的键值对。可以认为,键值对与baseURL是对应的关系。
  • 例外:申请用户授权的第二步时,会用到第一步里的跳转链接oauth_callback
  • 例外:申请用户授权的第二步时,由于用户需要用户授权,然后跳转,这一步,是没有返回值的。跳转之后的连接需要开发者手动拦截。
  • 例外:类似发推功能的API这种,我们只需要确认成功发推,并不关注返回值。此时可以设定返回值的键为空,不对其进行处理。

具体实施

第一步:Obtaining a request token

  1. 采用URLSessionDataTask链接,并处理传来的数据
  2. 打开WebViewViewController

第二步:Redirecting the user

  1. 通过内嵌的网页处理URLRequest
  2. 用户登录并授权后,Twitter会跳转到第一步中设定的会掉页面
  3. 通过WKNavigationDelegatefunc webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {,截获该跳转,获得数据。
  4. 此时需要验证本次收到的oauth_token与第一步收到的相同

第三步:Converting the request token to an access token

  1. 采用URLSessionDataTask链接,将第二步获得的oauth_verifier,发往服务器进行验证,获得最终的tokens,并保存。
  2. 授权完成。