美文网首页iOS开发你需要知道的iOS界面特效篇iOS 控件定制
iOS开发 悬浮窗口播放器简单实现 类似iPad画中画效果

iOS开发 悬浮窗口播放器简单实现 类似iPad画中画效果

作者: sands_yu | 来源:发表于2017-03-22 14:03 被阅读2583次

场景

公司新项目是一个直播类型的项目,要求实现类似熊猫or斗鱼那种退出直播详情界面衔接一个悬浮(可随意拖动)的播放器继续播放.
考虑到无缝衔接的需求和重新加载延迟缓冲的问题,大体定下一个思路是用一个单例对象来实现这个功能,单例对象包含一个播放器对象和一些需要用的参数等.

效果

-w415

实现

播放器使用了网易直播提供的NELivePlayer,集成该播放器可以参考网易官网的集成文档:http://vcloud.163.com/docs/live/player.html 播放器底层使用的是bilibili开源的ijkplaer

注:只能真机调试,模拟器播放器会创建失败.

流程:
    1.创建PlayerShowView对象传入直播url
    2.PlayerShowView内部获取PlayObj单利对象,传入直播url,获得播放器视图,添加到自身
    3.PlayObj获取到直播url,判断是否已经有创建的播放器对象 || 是否是正在播放的直播等做不同操作
    4.退出播放详情页-调用delloc时发送一个通知,在根视图控制器接受消息创建悬浮播放器

PlayObj.h

#import <Foundation/Foundation.h>
#import <NELivePlayer/NELivePlayer.h>
#import <NELivePlayer/NELivePlayerController.h>
#import "Masonry.h"
#import <YYKit.h>
#import "UIDevice+XJDevice.h"
#import "MLRefreshView.h"

@protocol PlayObjDelegate <NSObject>

- (void)PlayObjFull;

- (void)PlayObjclose;

- (void)PlayObjRestConnect;

- (void)PlayObjBack;

@end

@interface PlayObj : NSObject

/** 
 直播播放器 
 */
@property(nonatomic, strong) id<NELivePlayer> liveplayer;

/**
 播放url
 */
@property (nonatomic, copy) NSString* liveUrl;

/**
 是否悬浮窗口播放
 */
@property (nonatomic, assign) BOOL isSuspend;

/**
 是否全屏
 */
@property (nonatomic, assign) BOOL isFull;


@property (nonatomic, weak) id<PlayObjDelegate>delagete;

+ (PlayObj*)getInstance;

- (void)shutDown;

@end


PlayObj.m

//
//  PlayObj.m
//  ijkplayerDemo
//
//  Created by sands on 2017/3/5.
//  Copyright © 2017年 wanglei. All rights reserved.
//

#import "PlayObj.h"

@interface PlayObj()

/** 
 返回按钮
 */
@property (nonatomic, weak) UIButton *backButton;

/** 
 屏幕切换按钮
 */
@property (nonatomic, weak) UIButton *orientationButton;

/**
 关闭按钮
 */
@property (nonatomic, weak) UIButton *closeButton;

/**
 loadingView
 */
@property (nonatomic, weak) MLRefreshView *indicator;

/**
 加载提示
 */
@property (nonatomic, weak) UILabel* lodingTextLabel;

/**
 加载失败提示视图
 */
@property (nonatomic, weak) UIView* faildView;


/**
 定时器-判断加载超时
 */
@property (nonatomic, weak) NSTimer* inOutTimer;


@property (nonatomic, assign) NSInteger inOutNumber;

@end

static PlayObj *playObj = nil;
#define MAX_LODING_TIME 30 //最大加载时间 超过这个时间显示连接失败提示

@implementation PlayObj

+ (PlayObj*)getInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        playObj = [[PlayObj alloc]init];
    });
    return playObj;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.inOutTimer = 0;
    }
    return self;
}

#pragma mark =================defaultUI==================
- (void)defaultWithPlaye{
    
    self.liveplayer = [[NELivePlayerController alloc]
                       initWithContentURL:[NSURL URLWithString:self.liveUrl]];
    
    if (self.liveplayer == nil) {
        NSLog(@"failed to initialize!");
    }
    
    self.liveplayer.view.frame = CGRectMake(0, 64, CGRectGetWidth([UIScreen mainScreen].bounds), 210);
    
    self.liveplayer.view.backgroundColor = [UIColor blackColor];
    
    //设置播放缓冲策略,直播采用低延时模式或流畅模式,点播采用抗抖动模式,具体可参见API文档
    [self.liveplayer setBufferStrategy:NELPLowDelay];
    //设置画面显示模式,默认按原始大小进行播放,具体可参见API文档
    [self.liveplayer setScalingMode:NELPMovieScalingModeNone];
    //设置视频文件初始化完成后是否自动播放,默认自动播放
    [self.liveplayer setShouldAutoplay:YES];
    //设置是否开启硬件解码,IOS 8.0以上支持硬件解码,默认为软件解码
    [self.liveplayer setHardwareDecoder:YES];
    //设置播放器切入后台后时暂停还是继续播放,默认暂停
    [self.liveplayer setPauseInBackground:NO];
    
    [self.liveplayer prepareToPlay];
    
    [self defaultOtherUI];
    
    [self initNotification];
}

- (void)defaultOtherUI{
    if (_backButton != nil) {
        return;
    }
    @weakify(self);
    [self.backButton mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.top.mas_equalTo(@16);
        make.size.mas_equalTo(CGSizeMake(30.f,30.f));
    }];
    
    [self.orientationButton mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.right.bottom.mas_equalTo(@(-16));
        make.size.mas_equalTo(CGSizeMake(30.f, 30.f));
    }];
 
    [self.indicator mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerY.mas_equalTo(weak_self.liveplayer.view.mas_centerY);
        make.centerX.mas_equalTo(weak_self.liveplayer.view.mas_centerX);
        make.size.mas_equalTo(CGSizeMake(20.f,20.f));
    }];
    
    [self.lodingTextLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.mas_equalTo(weak_self.liveplayer.view.mas_centerX);
        make.top.equalTo(weak_self.indicator.mas_bottom).with.offset(5);
    }];
    
    [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(@10);
        make.right.mas_equalTo(@-10);
        make.size.mas_equalTo(CGSizeMake(15.f, 15.f));
    }];
    
    [self.faildView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(weak_self.liveplayer.view);
        make.center.mas_equalTo(weak_self.liveplayer.view);
    }];
    
}

#pragma mark sett

/**
 传入url初始化播发器

 @param liveUrl 直播地址
 */
- (void)setLiveUrl:(NSString *)liveUrl{
    _liveUrl = liveUrl;
    [self defaultWithPlaye];
}

/**
 根据isSuspend展示不同的OtherUI

 @param isSuspend 是否悬浮窗口
 */
- (void)setIsSuspend:(BOOL)isSuspend{
    _isSuspend = isSuspend;

    if (isSuspend) {
        self.backButton.hidden = true;
        self.orientationButton.hidden = true;
        self.closeButton.hidden = false;
    }
}


/**
 详情页内非全屏不显示返回按钮

 @param isFull 是否全屏
 */
- (void)setIsFull:(BOOL)isFull{
    _isFull = isFull;
    self.backButton.hidden = !_isFull;
    self.orientationButton.hidden = false;
    self.closeButton.hidden = true;
}

#pragma mark OtherUI (返回 放大 loding 关闭 加载失败)
- (UIButton*)backButton
{
    @weakify(self);
    if (!_backButton) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setImage:[UIImage imageNamed:@"player_backButton_icon_30x30_"] forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"player_backButton_pressIcon_30x30_"] forState:UIControlStateHighlighted];
        [button addBlockForControlEvents:UIControlEventTouchUpInside block:^(id  _Nonnull sender) {
            [weak_self.delagete PlayObjBack];
        }];
        [self.liveplayer.view addSubview:button];
        _backButton = button;
        _backButton.hidden = !_isFull;
        
    }
    return _backButton;
}

- (UIButton*)orientationButton
{
    if (!_orientationButton) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setImage:[UIImage imageNamed:@"player_fullScreen_icon_30x30_"] forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"player_fullScreen_pressIcon_30x30_"] forState:UIControlStateHighlighted];
        [button addTarget:self action:@selector(scaleFull) forControlEvents:UIControlEventTouchUpInside];
        [self.liveplayer.view addSubview:button];
        _orientationButton = button;
        
    }
    return _orientationButton;
}

- (MLRefreshView*)indicator{
    if (!_indicator) {
        MLRefreshView* indicator = [MLRefreshView refreshViewWithFrame:CGRectMake(0, 0, 20, 20) logoStyle:RefreshLogoNone];
        [self.liveplayer.view addSubview:indicator];
        _indicator = indicator;
        [self loadingStatus:YES];
    }
    return _indicator;
}

- (UILabel*)lodingTextLabel{
    if (!_lodingTextLabel) {
        UILabel* label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 20)];
        label.text  = @"走心加载中";
        label.backgroundColor = [UIColor clearColor];
        label.textColor = [UIColor whiteColor];
        label.textAlignment = NSTextAlignmentCenter;
        label.font = [UIFont systemFontOfSize:11];
        _lodingTextLabel = label;
        [self.liveplayer.view addSubview:label];
    }
    return _lodingTextLabel;
}

- (UIButton*)closeButton{
    @weakify(self);
    if (!_closeButton) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button setTitle:@"X" forState:UIControlStateNormal];
        [button setBackgroundColor:[UIColor redColor]];
        [button addBlockForControlEvents:UIControlEventTouchUpInside block:^(id  _Nonnull sender) {
            [weak_self.delagete PlayObjclose];
        }];
        [self.liveplayer.view addSubview:button];
        _closeButton = button;
        _closeButton.hidden = !_isSuspend;
    }
    return _closeButton;
}

- (UIView*)faildView{
    @weakify(self);
    if (!_faildView) {
        UIView* view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
        UIImageView* image = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"failure"]];
        image.tag = 101;
        image.frame = CGRectMake(0, 0, 100, 75);
        [view addSubview:image];
        [image mas_makeConstraints:^(MASConstraintMaker *make) {
            make.center.mas_equalTo(view);
        }];
        UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]initWithActionBlock:^(id  _Nonnull sender) {
            NSLog(@"faild View tap");
            [weak_self.delagete PlayObjRestConnect];
            _faildView.hidden = true;
        }];
        [view addGestureRecognizer:tap];
        [self.liveplayer.view addSubview:view];
        _faildView = view;
        _faildView.hidden = true;
    }
    return _faildView;
}


#pragma mark notify method
- (void)initNotification{
    // 播放器媒体流初始化完成后触发,收到该通知表示可以开始播放
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerDidPreparedToPlay:)
                                                 name:NELivePlayerDidPreparedToPlayNotification
                                               object:_liveplayer];
    
    // 播放器加载状态发生变化时触发,如开始缓冲,缓冲结束
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NeLivePlayerloadStateChanged:)
                                                 name:NELivePlayerLoadStateChangedNotification
                                               object:_liveplayer];
    
    // 正常播放结束或播放过程中发生错误导致播放结束时触发的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerPlayBackFinished:)
                                                 name:NELivePlayerPlaybackFinishedNotification
                                               object:_liveplayer];
    
    // 第一帧视频图像显示时触发的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerFirstVideoDisplayed:)
                                                 name:NELivePlayerFirstVideoDisplayedNotification
                                               object:_liveplayer];
    
    // 第一帧音频播放时触发的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerFirstAudioDisplayed:)
                                                 name:NELivePlayerFirstAudioDisplayedNotification
                                               object:_liveplayer];
    
    
    // 资源释放成功后触发的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerReleaseSuccess:)
                                                 name:NELivePlayerReleaseSueecssNotification
                                               object:_liveplayer];
    
    // 视频码流解析失败时触发的通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(NELivePlayerVideoParseError:)
                                                 name:NELivePlayerVideoParseErrorNotification
                                               object:_liveplayer];
}


#pragma 通知
- (void)NELivePlayerDidPreparedToPlay:(NSNotificationCenter*)not{
    NSLog(@"// 播放器媒体流初始化完成后触发,收到该通知表示可以开始播放");
    NSLog(@"_liveplayer = %@",_liveplayer);
}

- (void)NeLivePlayerloadStateChanged:(NSNotification*)not{
    switch (self.liveplayer.loadState) {
        case NELPMovieLoadStatePlayable:
            NSLog(@"NELPMovieLoadStatePlayable 播放器初始化完成,可以播放");
            break;
        case NELPMovieLoadStatePlaythroughOK:{
            NSLog(@"NELPMovieLoadStatePlaythroughOK 缓冲完成");
            [self loadingStatus:NO];
            [self.inOutTimer invalidate];
            _inOutNumber = 0;
        }
            break;
        case NELPMovieLoadStateStalled:
            NSLog(@"NELPMovieLoadStateStalled 缓冲 展示loding..");
            [self loadingStatus:YES];
            break;
        default:
            break;
    }
}

- (void)NELivePlayerPlayBackFinished:(NSNotification*)not{
    NSLog(@"// 正常播放结束或播放过程中发生错误导致播放结束时触发的通知");
    [self showFaildViewWithType:2];
}

- (void)NELivePlayerFirstVideoDisplayed:(NSNotificationCenter*)not{
    NSLog(@"// 第一帧视频图像显示时触发的通知");
    [self loadingStatus:NO];
    [self timeEnd];
}

- (void)NELivePlayerFirstAudioDisplayed:(NSNotificationCenter*)not{
    NSLog(@"// 第一帧音频播放时触发的通知");
}

- (void)NELivePlayerReleaseSuccess:(NSNotificationCenter*)not{
    NSLog(@"// 资源释放成功后触发的通知");
}

- (void)NELivePlayerVideoParseError:(NSNotificationCenter*)not{
    NSLog(@"// 视频码流解析失败时触发的通知");
}

#pragma mark Other Method
- (void)shutDown{
    [self.liveplayer shutdown];
    [self.liveplayer.view removeFromSuperview];
    self.liveplayer = nil;
    _liveUrl = @"";
    [self removePlaySub];
    [self timeEnd];
}

- (void)removePlaySub{
    _faildView = nil;
    _orientationButton = nil;
    _closeButton = nil;
    _indicator = nil;
    _lodingTextLabel = nil;
    _backButton = nil;
}

/**
 全屏
 */
- (void)scaleFull{
    [self.delagete PlayObjFull];
}

- (void)loadingStatus:(BOOL)status{
    _indicator.hidden = !status;
    _lodingTextLabel.hidden = _indicator.hidden;
    
    if (status) {
        [_indicator startAnimation];
        _inOutTimer = 0;
        self.inOutTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(checkLiveTimerOut:) userInfo:nil repeats:YES];
    }else{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [_indicator stopAnimation];
        });
    }
}

- (void)checkLiveTimerOut:(NSTimer*)timer{
    _inOutNumber++;
    NSLog(@"checkLiveTimerOut %ld",(long)_inOutNumber);
    if (_inOutNumber>=20) {
        _indicator.hidden = true;
        _lodingTextLabel.hidden = true;
        [self.liveplayer stop];
        [self showFaildViewWithType:1];
    }
}

- (void)timeEnd{
    [_inOutTimer invalidate];
    _inOutNumber = 0;
    _inOutTimer = nil;
}


/**
 展示错误提示View

 @param type 1:点击重连 2:主播下播
 */
- (void)showFaildViewWithType:(NSInteger)type{
    if (type == 1) {
        _faildView.hidden = false;
        _faildView.userInteractionEnabled = true;
    }else if(type == 2){
        UIImageView* imageView = [_faildView viewWithTag:101];
        if (imageView) {
            imageView.image = [UIImage imageNamed:@"live_icon_absent"];
        }
        _faildView.hidden = false;
        _faildView.userInteractionEnabled = false;
        [self loadingStatus:false];
    }
    [self timeEnd];
}
@end

PlayerShowView.h

//
//  PlayerShowView.h
//  NEPlyaer
//
//  Created by fhzx_mac on 2017/3/9.
//  Copyright © 2017年 sandsyu. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "Masonry.h"
#import <NELivePlayer/NELivePlayer.h>
#import <NELivePlayer/NELivePlayerController.h>

@interface PlayerShowView : UIView

- (instancetype)initWithFrame:(CGRect)frame connectWithUrl:(NSString*)url;

@property (nonatomic, strong) id<NELivePlayer> liveplayer;

@property (nonatomic, copy) NSString* url;

@property (nonatomic, assign) BOOL isFull;

@property (nonatomic, assign) BOOL isSuspend;

@property (nonatomic, assign) CGRect oldFrame;

@end

PlayerShowView.m

//
//  PlayerShowView.m
//  NEPlyaer
//
//  Created by fhzx_mac on 2017/3/9.
//  Copyright © 2017年 sandsyu. All rights reserved.
//

#import "PlayerShowView.h"
#import "PlayObj.h"

@interface PlayerShowView()<PlayObjDelegate>

@end

@implementation PlayerShowView

- (instancetype)initWithFrame:(CGRect)frame connectWithUrl:(NSString*)url
{
    self = [super initWithFrame:frame];
    if (self) {
        self.url = url;
        [self defaultUI];
    }
    return self;
}

- (void)defaultUI{
    
    @weakify(self);
    if ([PlayObj getInstance].liveUrl.length<=0) {
        [PlayObj getInstance].liveUrl = self.url;
    }else{
        self.url = [PlayObj getInstance].liveUrl;
    }
    [PlayObj getInstance].delagete = self;
    [self addSubview:[PlayObj getInstance].liveplayer.view];
    
    [self sendSubviewToBack:self.liveplayer.view];
    
    [[PlayObj getInstance].liveplayer.view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(weak_self);
        make.center.mas_equalTo(weak_self);
    }];
    
    [PlayObj getInstance].isSuspend = self.isSuspend;
}

- (void)setIsSuspend:(BOOL)isSuspend{
    _isSuspend = isSuspend;
    [PlayObj getInstance].isSuspend = _isSuspend;
}

- (void)setIsFull:(BOOL)isFull{
    _isFull = isFull;
    [PlayObj getInstance].isFull = isFull;
}

-(void)PlayObjFull{
    @weakify(self);
    if (!_isFull) {
        weak_self.oldFrame = weak_self.frame;
        weak_self.viewController.navigationController.navigationBar.hidden = true;
        [UIDevice setOrientation:UIInterfaceOrientationLandscapeRight];
        weak_self.frame = weak_self.window.bounds;
        weak_self.isFull = true;
    }else{
        weak_self.viewController.navigationController.navigationBar.hidden = false;
        [UIDevice setOrientation:UIInterfaceOrientationPortrait];
        weak_self.frame = weak_self.oldFrame;
        weak_self.isFull = false;
    }
}

- (void)PlayObjclose{
    [[PlayObj getInstance]shutDown];
    [self removeFromSuperview];
}

- (void)PlayObjRestConnect{
    [[PlayObj getInstance]shutDown];
    [self defaultUI];
}

- (void)PlayObjBack{
    [self PlayObjFull];
}

@end

使用:

    PlayerShowView* View = [[PlayerShowView alloc]initWithFrame:CGRectMake(0, 100, self.view.width, self.view.width*0.6)
                                                  connectWithUrl:self.liveUrl];
    View.isFull = false;
    View.isSuspend = false;
    [self.view addSubview:View];

具体实现可以参考github上的代码:
https://github.com/yushengchu/NEPlyaer

DEMO使用:
播放器静态库文件过大上传到百度云
https://pan.baidu.com/s/1i4FDtm1
下载解压,放入项目根目录(xcodeproj文件所在目录)运行即可

相关文章

  • iOS开发 悬浮窗口播放器简单实现 类似iPad画中画效果

    场景 公司新项目是一个直播类型的项目,要求实现类似熊猫or斗鱼那种退出直播详情界面衔接一个悬浮(可随意拖动)的播放...

  • iOS音乐播放器(下)

    本篇是音频播放器的悬浮窗篇,上一篇是播放器主窗口篇先上效果图: 窗口与右上角播放器入口互斥 窗口在部分页面显示,部...

  • iOS转android之listview悬浮效果

    相信从事iOS开发的人都使用过cell悬浮效果,在Android开发中系统没有提供这种效果,于是我们要手动实现,大...

  • iOS9 画中画 Picture in Picture

    画中画 (Picture in Picture) iOS9系统在iPad上支持多任务分屏和画中画视频播放,画中画视...

  • 画中画功能探究

    最近开始研究iOS14画中画功能的实现,最终分别通过使用AVPlayerViewController构建播放器和A...

  • ionic 实现类似 iOS 区头悬浮效果

    实现页面头部保持不动的方法,适配 Android 和 iOS。 页面示意图如下,main中的list向上滑动时,h...

  • Flutter 中悬浮窗口Widget之Overlay

    在开发中常常需要一个悬浮窗口来做各种筛选,实现悬浮控件需要知道悬浮控件应该出现在什么位置以及窗口的大小,而获取悬浮...

  • 移动端touch拖动事件和click事件冲突问题解决

    通过一个悬浮球交互功能的案例来阐述问题,以及解决办法。 实现效果 类似微信里的悬浮窗效果,苹果手机的悬浮球功能效果...

  • 2021-03-24

    简单实现基于IOS的音乐播放器,并且带有歌词,随播放自动滚动,实现效果如下: [图片上传失败...(image-8...

  • 高斯模糊

    高斯模糊 【iOS 开发】实现毛玻璃(高斯模糊)效果 - CocoaChina_让移动开发更简单

网友评论

  • zhigangcoding:跟斗鱼的差别还是挺大的吧,人家是退出有一个缩放的pop,点击小窗push进去是放大的进去一样
    zhigangcoding:@sands_yu 功能1:从直播详情页pop 出来的时候,貌似是缩放,但是,它好像是把下面的弹幕全部隐藏,而上面的播放稍微缩放了一点点; 功能2:点击小窗,又变大然后复原为播放页;请问这两个功能咋整呢?
    sands_yu:只是个demo 本来就不是仿造斗鱼写的 如果要过渡效果加个转场动画就好了
  • 6aae6f0aedb6:你好,我想知道从小窗返回原来窗口要什么实现,保存原来的播放时间
    sands_yu:全局下只存在一个播放器对象,从小窗口返回原来窗口使用的也还是这个对象 不需要做什么操作就会从原来播放的时间点继续去播放 应该关注的是如何让这个过渡更加的舒服
  • MUYO_echo:您好,我按照您的手势写的拖动,没有放视频的时候是正常的,视频播放中的话,拖动view,之后view又会回到原先位置,请求指导一下。万分感谢
  • Sun_Song_石头记:[self sendSubviewToBack:self.liveplayer.view]; PlayerShowView.h里面的这个方法,是什么意思? self.liveplayer是空的啊
  • Sun_Song_石头记:您好,我用了你这个类, 我进页面的时候是黑屏的, 但是已经走了方法了.....好烦
  • 西叶lv:这个,网易已经做好了么?
  • 3019fd6ce50a:demo编译过不了,报错,请问,悬浮视频,拖动效果代码在哪里实现的啊?貌似没有找到
    3019fd6ce50a:@sands_yu 已经做好了,谢谢
    sands_yu:AppDelegate.m中

本文标题: iOS开发 悬浮窗口播放器简单实现 类似iPad画中画效果

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