美文网首页iOS技术iOS开发系列iOS
iOS流媒体开发之二:滑动手势控制音量、亮度和进度

iOS流媒体开发之二:滑动手势控制音量、亮度和进度

作者: 张云龙 | 来源:发表于2016-07-11 15:34 被阅读5202次

    尊重知识,转发请注明出处:iOS流媒体开发之二:滑动手势控制音量、亮度和进度


    概要

    看到文章的标题,小伙伴们大概会有两种反应:①这和流媒体技术没关系吧②网上有很多这个功能的实现方案。
    1、对于第一种反应,从开发者的角度看这个确实不属于流媒体技术范畴,但是对于用户来讲这个已经是看视频时理所当然应该有的功能,密不可分,鉴于用户就是上帝,所以这个绝对属于流媒体范畴.
    2、第二种反应确实是事实,但是能真正完美实现的不多。有6s Plus的小伙伴可以打开爱奇艺或者新浪微博,随便打开一个视频,手势增大音量出现音量无法调节到最大的bug,会剩余4格音量,只能先调节到很小的音量再次增大才可以到最大音量,当然这个是系统在6s上的一个bug,但并非无法解决,即使是爱奇艺和新浪也没有完美实现(您看到此文章的时候有可能已经修复此bug)。还有一点一般的实现方案是添加UIPanGestureRecognizer手势,这个方案如果只是简单的视频播放器或者写一个Demo完全没问题,可是放在一个稍微复杂点的视频播放器会引起很多问题,后面再说,因此我这里提供另一种方案。

    新浪微博手势调节音量bug.png

    思考

    这个功能对于有一些开发经验的人来说并不难,只要勤快点,别遇到点难题没怎么思考就去找第三方库,都可以靠自己弄个八九不离十。我这里想通过这个功能向大家介绍我平时解决一些问题的心路历程,希望能帮到大家的不仅仅是技术本身,还有一种解决问题的能力。对于喜欢“废话少说,直接上代码”的小伙伴,这篇文章可能会让你有些不舒服,因为在我看来代码一文不值,值钱的是我们的思想,别急,咱们慢慢来。

    • 分析问题,抽离本质
      当我们拿到这个需求的时候,首先不要想我用哪些类可以实现,我要添加在哪个view上,我们首先要分析出这个问题的本质,对于手势调节这个问题的本质很简单,就是用户左右、上下滑动时根据手指移动的方向和偏移量改变音量等设置。
    • 捋顺逻辑,列出方案
      分析出问题的本质后,依然不要去想和代码有关的事情,接着我们要思考处理问题的逻辑并给出一个目前看来可行的方案。我平时会找一张白纸,画一些草图帮助记忆和理解,当然曾几度想用xmind等思维导图,但是发现还是朴实的白纸适合我,小伙伴们可以根据自身选择,只要能帮助捋顺思路就可以了。
      第一步我们要检测到手指在屏幕上的滑动,可以使用UIPanGestureRecognizer手势,前面说了这个会引起很多问题,比如一个视频播放器不仅仅有滑动手势还是单击、双击、左滑退出UINavigationController,各种手势会冲突,其次我们有时会在拖动前或者结束后处理一些事情在再执行拖动,虽然UIPanGestureRecognizer有UIGestureRecognizerStateBegan、UIGestureRecognizerStateEnded这些状态,可是手指点击屏幕没有滑动的时候无法触发,因此我最后弃用这个方案,改为使用- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;系列方法,具体实现看后面代码; 第二步要判断用户向哪个方向移动了,以便调用相应的设置,这可以根据手指在上下左右的偏移量来计算,我的方案是设置一个初始偏移量的数值,比如30,上下左右哪个方向偏移量先到达30,手势就设定为对应的方向; 第三步就是调用对应的方法去改变音量、亮度和进度了,具体实现看后面。

    行动

    逻辑通了,方案有了,现在开始行动吧,首先你要有个可以播放视频demo,最好是点播视频,可参考上篇博文:iOS流媒体开发之一:总结系统提供的接口,这里不赘述。

    • 自定义一个UIButton
      我们需要在视频上添加一个透明的button来捕捉和响应用户的点击滑动屏幕的事件,同时在.h文件中声明一个代理,向相应的页面传递自定义button的响应事件,代码如下:

    .h文件

        #import <UIKit/UIKit.h>
    
        @protocol ZYLButtonDelegate <NSObject>
    
        /**
         * 开始触摸
         */
        - (void)touchesBeganWithPoint:(CGPoint)point;
    
        /**
         * 结束触摸
         */
        - (void)touchesEndWithPoint:(CGPoint)point;
    
        /**
         * 移动手指
         */
        - (void)touchesMoveWithPoint:(CGPoint)point;
    
        @end
    
        @interface ZYLButton : UIButton
    
        /**
         * 传递点击事件的代理
         */
        @property (weak, nonatomic) id <ZYLButtonDelegate> touchDelegate;
    
        @end
    

    .m文件
    #import "ZYLButton.h"

        @implementation ZYLButton
    
        //触摸开始
        - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
            [super touchesBegan:touches withEvent:event];
            //获取触摸开始的坐标
            UITouch *touch = [touches anyObject];
            CGPoint currentP = [touch locationInView:self];
            [self.touchDelegate touchesBeganWithPoint:currentP];
        }
    
        //触摸结束
        - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
            [super touchesEnded:touches withEvent:event];
            UITouch *touch = [touches anyObject];
            CGPoint currentP = [touch locationInView:self];
            [self.touchDelegate touchesEndWithPoint:currentP];
        }
    
        //移动
        - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
            UITouch *touch = [touches anyObject];
            CGPoint currentP = [touch locationInView:self];
            [self.touchDelegate touchesMoveWithPoint:currentP];
        }
    
        @end
    

    注意:之所以选择UIButton有2点原因: 1、不用手动开启userInteractionEnabled用户交互 2、同时可以很方便的为UIButton添加Target,不需要像UIView那样在再定义一个UITapGestureRecognizer,当然UIButton添加各种状态的背景颜色各背景图也要比UIView方便得多。

    • 将自定义的Button添加到视频上
      //添加自定义的Button到视频画面上
      self.button = [[ZYLButton alloc] initWithFrame:playerLayer.frame];
      self.button.touchDelegate = self;
      [playerView addSubview:self.button];

    • 在代理方法中改变定音量、亮度和进度
      首先定义个一枚举表示上下左右,这里只需要判断手指是上下还是左右滑动
      typedef NS_ENUM(NSUInteger, Direction) {
      DirectionLeftOrRight,
      DirectionUpOrDown,
      DirectionNone
      };
      同时声明一个表示方向的变量、一个记录用户触摸视频时的坐标变量、一个记录用户触摸视频时的亮度和音量大小的变量和一个记录用户触摸屏幕是视频进度的变量
      @property (assign, nonatomic) Direction direction;
      @property (assign, nonatomic) CGPoint startPoint;
      @property (assign, nonatomic) CGFloat startVB;
      @property (assign, nonatomic) CGFloat startVideoRate;

    • 刚开始触摸视频的代理
      当用户首次触摸视频时,记录首次触摸的坐标、当前音量或者亮度、当前视频的进度,为了获取当前音量要首先定义MPVolumeView、 UISlider
      @property (strong, nonatomic) MPVolumeView *volumeView;//控制音量的view

        @property (strong, nonatomic) UISlider* volumeViewSlider;//控制音量
      
      
        //设置self.volumeView的frame
        self.volumeView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.width * 9.0 / 16.0);
      

    在getter方法中初始化:
    - (MPVolumeView *)volumeView {
    if (_volumeView == nil) {
    _volumeView = [[MPVolumeView alloc] init];
    [_volumeView sizeToFit];
    for (UIView view in [_volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
    self.volumeViewSlider = (UISlider
    )view;
    break;
    }
    }
    }
    return _volumeView;
    }

    然后在开始触摸的代理里记录
    #pragma mark - 开始触摸

        - (void)touchesBeganWithPoint:(CGPoint)point {
            //记录首次触摸坐标
            self.startPoint = point;
            //检测用户是触摸屏幕的左边还是右边,以此判断用户是要调节音量还是亮度,左边是亮度,右边是音量
            if (self.startPoint.x <= self.button.frame.size.width / 2.0) {
                //亮度
                self.startVB = [UIScreen mainScreen].brightness;
            } else {
                //音量
                self.startVB = self.volumeViewSlider.value;
            }
        }
        CMTime ctime = self.avPlayer.currentTime;
        self.startVideoRate = ctime.value / ctime.timescale / self.total;
    
    • 接着在拖动的代理方法里改变音量、亮度和进度
      #pragma mark - 拖动
      - (void)touchesMoveWithPoint:(CGPoint)point {
      //得出手指在Button上移动的距离
      CGPoint panPoint = CGPointMake(point.x - self.startPoint.x, point.y - self.startPoint.y);
      //分析出用户滑动的方向
      if (self.direction == DirectionNone) {
      if (panPoint.x >= 30 || panPoint.x <= -30) {
      //进度
      self.direction = DirectionLeftOrRight;
      } else if (panPoint.y >= 30 || panPoint.y <= -30) {
      //音量和亮度
      self.direction = DirectionUpOrDown;
      }
      }

            if (self.direction == DirectionNone) {
                return;
            } else if (self.direction == DirectionUpOrDown) {
                //音量和亮度
                if (self.startPoint.x <= self.button.frame.size.width / 2.0) {
            //调节亮度
                    if (panPoint.y < 0) {
                        //增加亮度
                        [[UIScreen mainScreen] setBrightness:self.startVB + (-panPoint.y / 30.0 / 10)];
                    } else {
                        //减少亮度
                        [[UIScreen mainScreen] setBrightness:self.startVB - (panPoint.y / 30.0 / 10)];
                    }
            
                } else {
                    //音量
                    if (panPoint.y < 0) {
                         //增大音量
                        [self.volumeViewSlider setValue:self.startVB + (-panPoint.y / 30.0 / 10) animated:YES];
                        if (self.startVB + (-panPoint.y / 30 / 10) - self.volumeViewSlider.value >= 0.1) {
                            [self.volumeViewSlider setValue:0.1 animated:NO];
                            [self.volumeViewSlider setValue:self.startVB + (-panPoint.y / 30.0 / 10) animated:YES];
                        }
                
                    } else {
                        //减少音量
                        [self.volumeViewSlider setValue:self.startVB - (panPoint.y / 30.0 / 10) animated:YES];
                    }
                }
            } else if (self.direction == DirectionLeftOrRight ) {
                //进度
                CGFloat rate = self.startVideoRate + (panPoint.x / 30.0 / 20.0);
                if (rate > 1) {
                    rate = 1;
                } else if (rate < 0) {
                    rate = 0;
                }
            }
      }
      

    注意: 1、前面提到一个增大音量的bug,我这里的解决办法是如果检测到用户设置的音量比系统当前的音量大于0.1,表示此时设置的音量已经无效了,然后把音量设置为0.1后再设置为我们设置无效的那个音量就可以了,具体做法参考代码; 2、在修改视频播放进度的时候,最好不在移动的方法中实时修改视频播放进度,一方面会造成卡顿的现象,一方面没有必要这么做,所以这里只是记录了用户想要调整的进度,然后在触摸结束的方法中设置进度; 3、这改变音量会调用系统自己的UI显示音量大小,但是在设置亮度的时候是系统没有提供相应的UI,需要我们自己设置,这个小伙伴们按照项目的需求自行添加一个UI效果就好了。

    • 触摸结束的代理
      #pragma mark - 结束触摸
      - (void)touchesEndWithPoint:(CGPoint)point {
      if (self.direction == DirectionLeftOrRight) {
      [self.avPlayer seekToTime:CMTimeMakeWithSeconds(self.total * self.currentRate, 1) completionHandler:^(BOOL finished) {
      //在这里处理进度设置成功后的事情
      }];
      }
      }

    真机运行效果如图


    Demo.png

    尾巴

    到这里我们就完成了手指滑动视频控制音量、亮度和进度的需求,大家在自己的项目中实际应用时还需要根据项目需要添加适当的UI,处理其他一些逻辑和业务流程。希望大家通过这个小小的示例可以有自己的解决问题的思路和流程,力争将一个即使很简单的功能做到尽量完美。Demo可以在这里下载:示例<small>Demo</small>

    相关文章

      网友评论

      • SoaringHeart:刚运行就卡顿了

        iOS音视频播放总结1[14993:5699828] 卡顿了
      • 唳天飞鹰:最开始要设置下slider的value为系统的当前值,否则最开始会是0:[_volumeViewSlider setValue:[[AVAudioSession sharedInstance] outputVolume] animated:NO];
        张云龙:@唳天飞鹰 是的,我的这个要完善
      • foolish_hungry:如果是对角线滑动怎么处理? 且每次滑动结束之后要需要重置方向值吗?
        张云龙:@foolish_hungry 仔细看文章,有答案
      • 心语风尚:用UIslider作为 进度条 正在播放时候 会不断给slider的value赋值 这个时候拖动滑块 滑块 多数情况都没有反应 怎么解决
      • de35e4131978:说了一大堆,还是有很多问题
      • 1a07b0493285:菜鸟求解:
        //进度
        CGFloat rate = self.startVideoRate + (panPoint.x / 30.0 / 20.0);
        为啥这里要 x轴偏移量/30.0/20.0?
        张云龙:@initData 文章里提到了,这里你完全可以自己控制,不需要照搬我的
      • 夏了_南城:当屏幕旋转的时候怎么才能让 那个声音的slider 也能转过来呢
      • 范德萨范德萨范德萨:这个 加减音量方案 会不会被拒
        张云龙:@程天聪_AKA_Tech 我这里给出的是一种大体的思路,具体的细节还要在实际应用时优化。我的项目这里是优化过的,只有在部分情况才会出现音量无法调节到最大的情况,因此不是每次都跳到很小的音量再变大,其次对最大音量也要做限制,若不限制,当调节的音量大于1是扬声器会出现斯斯的噪音。这些都比较简单,所有没有在demo里写。
        范德萨范德萨范德萨:@张云龙 嗯,用了很多,发现应该都是这个方案,虽然player有自己的音量,但是没这个好用。

        不过我有个改进的地方【self.startVB = self.volumeViewSlider.value;】这里在touchesBegan中记录初始音量,可以使用这个【startVolume = AVAudioSession.sharedInstance().outputVolume】,我反正如果用你这种记录,刚开始的时候这个slider的值总是0,就是说一旦调节音量,我都是从零开始,不知道你出现了吗,反正用我自己这个方案是ok。
        张云龙:@程天聪_AKA_Tech 不会的,我的线上项目已经用了。
      • BeijingIamback:写的太好了,谢谢
      • const_zhou:你好,想请教两个问题。
        1、看你的代码里没有给亮度调节添加视图显示进度,直接改变了屏幕的亮度。如果添加了一个视图,在调节音量后立即调节亮度,亮度视图会被音量视图挡住(MPVolumeView的消失会有一个动画延迟,大概1-2s)如何解决这个问题?MPVolumeView视图会一直在window的最上面。
        2、在视频播放的过程中快速调节音量会使视频播放卡顿,请问是否遇到?是否有解决方案?
        谢谢!
        张云龙:@const_zhou 刚才测试了下,第一个问题即使放在最上面的UIWindow上也还是会被系统的音量指示UI覆盖,这个接受不了的话换一种实现方案。第二个问题,我下载了熊猫TV,快速调节音量视频并没有卡顿,你说的卡顿不会是音量变大变小吧?
        const_zhou:@张云龙 第一个问题,已经尝试加在UIWindow上面,未能解决。第二个问题,是快速调节音量,不是调节进度条。(第一个问题可以在斗鱼上面稳定复现,第二个问题可以在熊猫tv上稳定复现)
        张云龙:@const_zhou 这两个问题文中基本都有答案。第一个问题是系统没有亮度指示UI,自己做的话可以控制显示在UIWindow最上面的,去UIApplication里面取出UIWindow数组,找到最上方那个添加上去就可以了。
        第二个问题很好解决啊,文中提到了,视频进度不要实时改变,用户在滑动的时候不改变进度,当用户调节完毕得到最后的值再去设置进度就好了,我的demo就是这么实现的。
      • Super_龙:您好, 写的非常好! 请教一个问题, 有一个tableview上边的cell只想让上边两个是那种大的, 剩下的都是小的, 然后当滑动到第二个cell的下边是开始随着偏移量变大, 直到达到头两个cell的大小. 若中间停止滑动则看是否达到规定一般, 达到则变大, 否则还原原大小. 这样的效果该怎样实现呢? 方便的话帮忙解答一下, 谢谢!
        张云龙:@Super_龙 嗯嗯,加油:+1:
        Super_龙:虽然还是不一样不过应该有相同的地方, 我参考一下他这个是怎么实现的然后我改一下看看怎样. 真的非常感谢
        张云龙:@Super_龙 需要用动画,参考下这个:http://ios.jobbole.com/87139/

      本文标题:iOS流媒体开发之二:滑动手势控制音量、亮度和进度

      本文链接:https://www.haomeiwen.com/subject/bcerjttx.html