【问题标题】:iOS - Can't stream video from Parse BackendiOS - 无法从 Parse 后端流式传输视频
【发布时间】:2016-05-16 02:10:42
【问题描述】:

最近我创建了自己的解析服务器,托管在 heroku 上,使用 mongoLab 来存储我的数据。

我的问题是我将视频保存为解析 PFFile,但是保存后我似乎无法流式传输它。

这是我的具体步骤。

首先,我保存UIImagePicker返回的视频

//Get the video URL
let videoURL = info[UIImagePickerControllerMediaURL] as? NSURL

//Create PFFile with NSData from URL
let data = NSData(contentsOfURL: videoURL!)
videoFile = PFFile(data: data!, contentType: "video/mp4")

//Save PFFile first, then save the PFUser
PFUser.currentUser()?.setObject(videoFile!, forKey: "profileVideo")
            videoFile?.saveInBackgroundWithBlock({ (succeeded, error) -> Void in
                print("saved video")
                PFUser.currentUser()?.saveInBackgroundWithBlock({ (succeeded, error) -> Void in
                    if succeeded && error == nil {
                        print("user saved")

                        //Hide progress bar 
                        UIView.animateWithDuration(0.5, animations: { () -> Void in                   
                            self.progressBar.alpha = 0
                            }, completion: { (bool) -> Void in
                                self.progressBar.removeFromSuperview()
                        })

                    }else{

                        //Show error if the save failed
                        let message = error!.localizedDescription
                        let alert = UIAlertController(title: "Uploading profile picture error!", message: message, preferredStyle: UIAlertControllerStyle.Alert)
                        let dismiss = UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)
                        alert.addAction(dismiss)
                        self.presentViewController(alert, animated: true, completion: nil)

                    }
                })
                }, progressBlock: { (progress) -> Void in
                    self.progressBar.setProgress(Float(progress)/100, animated: true)
            })

这一切都很好。问题出在我检索PFFile 并尝试流式传输视频时。这是我的代码:

//Get URL from my current user
self.videoFile = PFUser.currentUser()?.objectForKey("profileVideo") as? PFFile
                        self.profileVideoURL = NSURL(string: (self.videoFile?.url)!)

//Create AVPlayerController
let playerController = AVPlayerViewController()

//Set AVPlayer URL to where the file is stored on the sever
let avPlayer = AVPlayer(URL: self.profileVideoURL)
playerController.player = avPlayer

//Present the playerController
self.presentViewController(playerController, animated: true, completion: { () -> Void in
 playerController.player?.play()
})

当我展示playerController 时,最终发生的事情是这样的:

为什么在我尝试流式传输视频时会出现这种情况?

非常感谢任何帮助!

更新

我最近尝试使用以下代码行播放从不同数据库保存的视频:let videoURL = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")

这确认是我保存 PFFile 的格式导致了错误。

一定是这一行导致错误,因为"video/mp4" 可能不是正确的格式:videoFile = PFFile(data: data!, contentType: "video/mp4")

更新 2

我获取了位于 mongoLab 上的 .mp4 文件的直接链接,发现我可以在 google chrome 中播放它,但不能在 safari 或我的 iPhone 上播放。

更新 3

我发现这是解析 api 本身的问题,与代码无关,因为我的代码在使用原始解析后端(即将关闭的后端)而不是我的自定义解析服务器时运行良好.我目前没有解决方案,但随着时间的推移它应该会得到修复。

【问题讨论】:

  • 我遇到了类似(相同?)的问题。你能解释一下你的第三次更新吗?是否已向 GH 上的维护人员报告了这一点?
  • @Josh 这已经在 GH 中被多次报告过,但都是徒劳的。他们只是将其作为服务器问题关闭。有些人报告说从 mLab 切换到 S3 取得了成功,但也有其他人报告了从 S3 切换到相同的问题。截至目前,解析服务器 -heroku- mLab 没有流式传输音频,视频文件是我们可以得出的结论。如果有人能解决这个问题,我们应该通过在此处发布解决方案来帮助自己,我认为 Parse 工作人员不会解决这个问题。

标签: ios video streaming pffile parse-server


【解决方案1】:

对于仍然登陆这里的任何人,在查看新的 Parse-Server-example 更新时,Parse-server 现在可以识别流;但是,您应该使用 Parse iOS sdk 方法来检索视频。如果您要推出自定义解析服务器,则服务器代码如下。接下来是一些流式传输方法的列表。

在以下位置发现服务器代码更改:

parse-server-example\node_modules\parse-server\lib\Routers\FilesRouter

{
key: 'getHandler',
value: function getHandler(req, res) {
  var config = new _Config2.default(req.params.appId);
  var filesController = config.filesController;
  var filename = req.params.filename;
  var contentType = _mime2.default.lookup(filename);
  if (isFileStreamable(req, filesController)) {
    filesController.getFileStream(config, filename).then(function (stream) {
      handleFileStream(stream, req, res, contentType);
    }).catch(function () {
      res.status(404);
      res.set('Content-Type', 'text/plain');
      res.end('File not found.');
    });
  } else {
    filesController.getFileData(config, filename).then(function (data) {
      res.status(200);
      res.set('Content-Type', contentType);
      res.set('Content-Length', data.length);
      res.end(data);
    }).catch(function () {
      res.status(404);
      res.set('Content-Type', 'text/plain');
      res.end('File not found.');
    });
  }
}},

使用iOS parse sdk(swift)进行流式传输的方法示例:

vidObject.video.getDataStreamInBackground(block: <#T##PFDataStreamResultBlock?##PFDataStreamResultBlock?##(InputStream?, Error?) -> Void#>)

【讨论】:

    【解决方案2】:

    我有一个带有解析服务器的本地 mongodb,需要它来使其工作:

    Parse-server stream video to IOS from PFFile.url

    不知道是否与非本地数据库相同。

    【讨论】:

    • 如果您有新问题,请点击 按钮提出问题。如果您有足够的声誉,you may upvote 的问题。或者,将其“加注”为收藏夹,您将收到任何新答案的通知。
    【解决方案3】:

    parse-server 似乎不支持 Safari/iOS 中的流式传输,解决方案是使用 express 和 GridStore 启用它,如下所示,

    parse-server-example\node_modules\parse-server\lib\Routers\FilesRouter

        {
    key: 'getHandler',
    value: function getHandler(req, res, content) {
    var config = new _Config2.default(req.params.appId);
    var filesController = config.filesController;
    var filename = req.params.filename;
    var video = '.mp4'
    var lastFourCharacters = video.substr(video.length - 4);
    if (lastFourCharacters == '.mp4') {
    
      filesController.handleVideoStream(req, res, filename).then(function (data) {
    
        }).catch(function (err) {
          console.log('404FilesRouter');
            res.status(404);
            res.set('Content-Type', 'text/plain');
            res.end('File not found.');
        });
    }else{
    filesController.getFileData(config, filename).then(function (data) {
      res.status(200);
      res.end(data);
    }).catch(function (err) {
        res.status(404);
        res.set('Content-Type', 'text/plain');
        res.end('File not found.');
      });
     }
    }
    } , ...
    

    parse-server-example\node_modules\parse-server\lib\Controllers\FilesController

    _createClass(FilesController, [{
    key: 'getFileData',
    value: function getFileData(config, filename) {
    return this.adapter.getFileData(filename);
    }
    },{
    key: 'handleVideoStream',
    value: function handleVideoStream(req, res, filename) {
    return this.adapter.handleVideoStream(req, res, filename);
    }
    }, ...
    

    parse-server-example\node_modules\parse-server\lib\Adapters\Files\GridStoreAdapter

    ... , {
         key: 'handleVideoStream',
         value: function handleVideoStream(req, res, filename) {
         return this._connect().then(function (database) {
         return _mongodb.GridStore.exist(database, filename).then(function     () {
      var gridStore = new _mongodb.GridStore(database, filename, 'r');
      gridStore.open(function(err, GridFile) {
          if(!GridFile) {
              res.send(404,'Not Found');
              return;
            }
            console.log('filename');
            StreamGridFile(GridFile, req, res);
          });
          });
          })
          }
          }, ...
    

    GridStore 适配器底部

    function StreamGridFile(GridFile, req, res) {
    var buffer_size = 1024 * 1024;//1024Kb
    
    if (req.get('Range') != null) { //was: if(req.headers['range'])
      // Range request, partialle stream the file
      console.log('Range Request');
      var parts = req.get('Range').replace(/bytes=/, "").split("-");
      var partialstart = parts[0];
      var partialend = parts[1];
      var start = partialstart ? parseInt(partialstart, 10) : 0;
      var end = partialend ? parseInt(partialend, 10) : GridFile.length - 1;
      var chunksize = (end - start) + 1;
    
      if(chunksize == 1){
        start = 0;
        partialend = false;
      }
    
      if(!partialend){
        if(((GridFile.length-1) - start) < (buffer_size) ){
            end = GridFile.length - 1;
        }else{
          end = start + (buffer_size);
        }
          chunksize = (end - start) + 1;
        }
    
        if(start == 0 && end == 2){
          chunksize = 1;
        }
    
    res.writeHead(206, {
          'Cache-Control': 'no-cache',
        'Content-Range': 'bytes ' + start + '-' + end + '/' + GridFile.length,
        'Accept-Ranges': 'bytes',
        'Content-Length': chunksize,
        'Content-Type': 'video/mp4',
      });
    
      GridFile.seek(start, function () {
        // get GridFile stream
    
                var stream = GridFile.stream(true);
                var ended = false;
                var bufferIdx = 0;
                var bufferAvail = 0;
                var range = (end - start) + 1;
                var totalbyteswanted = (end - start) + 1;
                var totalbyteswritten = 0;
                // write to response
                stream.on('data', function (buff) {
                bufferAvail += buff.length;
                //Ok check if we have enough to cover our range
                if(bufferAvail < range) {
                //Not enough bytes to satisfy our full range
                    if(bufferAvail > 0)
                    {
                    //Write full buffer
                      res.write(buff);
                      totalbyteswritten += buff.length;
                      range -= buff.length;
                      bufferIdx += buff.length;
                      bufferAvail -= buff.length;
                    }
                }
                else{
    
                //Enough bytes to satisfy our full range!
                    if(bufferAvail > 0) {
                      var buffer = buff.slice(0,range);
                      res.write(buffer);
                      totalbyteswritten += buffer.length;
                      bufferIdx += range;
                      bufferAvail -= range;
                    }
                }
    
                if(totalbyteswritten >= totalbyteswanted) {
                //  totalbytes = 0;
                  GridFile.close();
                  res.end();
                  this.destroy();
                }
                });
            });
    
      }else{
    
    //  res.end(GridFile);
            // stream back whole file
          res.header('Cache-Control', 'no-cache');
          res.header('Connection', 'keep-alive');
          res.header("Accept-Ranges", "bytes");
          res.header('Content-Type', 'video/mp4');
          res.header('Content-Length', GridFile.length);
          var stream = GridFile.stream(true).pipe(res);
       }
      };
    

    附言 @Bragegs 在这里给出了原始答案 - https://github.com/ParsePlatform/parse-server/issues/1440#issuecomment-212815625 。用户@Stav1 在这个帖子中也提到了它,但不幸的是他被否决了?

    【讨论】:

      【解决方案4】:

      这段代码对我有用

      let playerController = AVPlayerViewController()
      
            self.addChildViewController(playerController)
            self.view.addSubview(playerController.view)
            playerController.view.frame = self.view.frame
      
            file!.getDataInBackgroundWithBlock({
              (movieData: NSData?, error: NSError?) -> Void in
              if (error == nil) {
      
                let filemanager = NSFileManager.defaultManager()
      
                let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
                let destinationPath:NSString = documentsPath.stringByAppendingString("/file.mov")
                movieData!.writeToFile ( destinationPath as String, atomically:true)
      
      
                let playerItem = AVPlayerItem(asset: AVAsset(URL: NSURL(fileURLWithPath: destinationPath as String)))
                let player = AVPlayer(playerItem: playerItem)
                playerController.player = player
                player.play()
              } else {
                print ("error on getting movie data \(error?.localizedDescription)")
              }
            })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-17
        • 1970-01-01
        • 2014-06-15
        • 2016-01-07
        • 1970-01-01
        • 2021-07-29
        • 1970-01-01
        • 2012-02-09
        相关资源
        最近更新 更多