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

场景

公司新项目是一个直播类型的项目,要求实现类似熊猫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文件所在目录)运行即可

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,799评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 做了一场梦,像站在时光罅隙宇宙流光里看尽了世事变迁,梦却了无痕迹。当初佛祖菩提树下一朝悟道明心见性,是不是也会有这...
    将离丶阅读 208评论 0 3
  • 我看见 远方新娘的头纱掉落悬崖 迷蒙的双眼 让我从梦中惊醒 冰冷的室温冻坏了心脏 灵魂渐渐抽离 只留下一架躯体 ...
    东邻子阅读 177评论 0 1
  • “我给你好的吃,好的穿,给你买玩具,带你去游乐园,为什么你又惹我生气?为什么把家里弄得乱糟糟?为什么?为什么?.....
    小眼豆豆米阅读 726评论 0 0