03.视频播放器Api说明

03.视频播放器Api说明

目录介绍

  • 01.最简单的播放
  • 02.如何切换视频内核
  • 03.切换视频模式
  • 04.切换视频清晰度
  • 05.视频播放监听
  • 06.列表中播放处理
  • 07.悬浮窗口播放
  • 08.其他重要功能Api
  • 09.播放多个视频
  • 10.VideoPlayer相关Api
  • 11.Controller相关Api
  • 12.边播放边缓存api
  • 13.类似抖音视频预加载
  • 14.视频播放器埋点

00.视频播放器通用框架

  • 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
  • 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
  • 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
  • 该播放器整体架构:播放器内核(自由切换) + 视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
  • 项目地址:https://github.com/yangchong211/YCVideoPlayer
  • 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343

01.最简单的播放

  • 必须需要的四步骤代码如下所示
    //创建基础视频播放器,一般播放器的功能
    BasisVideoController controller = new BasisVideoController(this);
    //设置控制器
    mVideoPlayer.setVideoController(controller);
    //设置视频播放链接地址
    mVideoPlayer.setUrl(url);
    //开始播放
    mVideoPlayer.start();
    
  • 开始播放
    //播放视频
    videoPlayer.start();
    

02.如何切换视频内核

  • 创建视频播放器
    PlayerFactory playerFactory = IjkPlayerFactory.create();
    IjkVideoPlayer ijkVideoPlayer = (IjkVideoPlayer) playerFactory.createPlayer(this);
    PlayerFactory playerFactory = ExoPlayerFactory.create();
    ExoMediaPlayer exoMediaPlayer = (ExoMediaPlayer) playerFactory.createPlayer(this);
    PlayerFactory playerFactory = MediaPlayerFactory.create();
    AndroidMediaPlayer androidMediaPlayer = (AndroidMediaPlayer) playerFactory.createPlayer(this);
    
  • 如何配置视频内核
    //播放器配置,注意:此为全局配置,例如下面就是配置ijk内核播放器
    VideoViewManager.setConfig(VideoPlayerConfig.newBuilder()
            .setLogEnabled(true)//调试的时候请打开日志,方便排错
            .setPlayerFactory(IjkPlayerFactory.create())
            .build());
    
  • 切换视频内核处理代码
    @SuppressLint("SetTextI18n")
    private void setChangeVideoType(@ConstantKeys.PlayerType int type){
        //切换播放核心,不推荐这么做,我这么写只是为了方便测试
        VideoPlayerConfig config = VideoViewManager.getConfig();
        try {
            Field mPlayerFactoryField = config.getClass().getDeclaredField("mPlayerFactory");
            mPlayerFactoryField.setAccessible(true);
            PlayerFactory playerFactory = null;
            switch (type) {
                case ConstantKeys.VideoPlayerType.TYPE_IJK:
                    playerFactory = IjkPlayerFactory.create();
                    mTvTitle.setText("视频内核:" + " (IjkPlayer)");
                    break;
                case ConstantKeys.VideoPlayerType.TYPE_EXO:
                    playerFactory = ExoPlayerFactory.create();
                    mTvTitle.setText("视频内核:" + " (ExoPlayer)");
                    break;
                case ConstantKeys.VideoPlayerType.TYPE_NATIVE:
                    playerFactory = MediaPlayerFactory.create();
                    mTvTitle.setText("视频内核:" + " (MediaPlayer)");
                    break;
                case ConstantKeys.VideoPlayerType.TYPE_RTC:
                    break;
            }
            mPlayerFactoryField.set(config, playerFactory);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

03.切换视频模式

  • 关于全屏模式相关api
    //进入全屏
    mVideoPlayer.startFullScreen();
    //退出全屏
    mVideoPlayer.stopFullScreen();
    
  • 关于小窗口播放相关api
    //开启小屏
    mVideoPlayer.startTinyScreen();
    //退出小屏
    mVideoPlayer.stopTinyScreen();
    

04.切换视频清晰度

05.视频播放监听

  • 这个分为两部分:第一部分是播放模式监听,第二部分是播放状态监听,暴露给开发者。这里不建议使用0,1,非常不方便简明之意,采用注解限定。
    mVideoPlayer.setOnStateChangeListener(new OnVideoStateListener() {
        /**
         * 播放模式
         * 普通模式,小窗口模式,正常模式三种其中一种
         * MODE_NORMAL              普通模式
         * MODE_FULL_SCREEN         全屏模式
         * MODE_TINY_WINDOW         小屏模式
         * @param playerState                       播放模式
         */
        @Override
        public void onPlayerStateChanged(int playerState) {
            switch (playerState) {
                case ConstantKeys.PlayMode.MODE_NORMAL:
                    //普通模式
                    break;
                case ConstantKeys.PlayMode.MODE_FULL_SCREEN:
                    //全屏模式
                    break;
                case ConstantKeys.PlayMode.MODE_TINY_WINDOW:
                    //小屏模式
                    break;
            }
        }
    
        /**
         * 播放状态
         * -1               播放错误
         * 0                播放未开始
         * 1                播放准备中
         * 2                播放准备就绪
         * 3                正在播放
         * 4                暂停播放
         * 5                正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
         * 6                暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
         * 7                播放完成
         * 8                开始播放中止
         * @param playState                         播放状态,主要是指播放器的各种状态
         */
        @Override
        public void onPlayStateChanged(int playState) {
            switch (playState) {
                case ConstantKeys.CurrentState.STATE_IDLE:
                    //播放未开始,初始化
                    break;
                case ConstantKeys.CurrentState.STATE_START_ABORT:
                    //开始播放中止
                    break;
                case ConstantKeys.CurrentState.STATE_PREPARING:
                    //播放准备中
                    break;
                case ConstantKeys.CurrentState.STATE_PREPARED:
                    //播放准备就绪
                    break;
                case ConstantKeys.CurrentState.STATE_ERROR:
                    //播放错误
                    break;
                case ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING:
                    //正在缓冲
                    break;
                case ConstantKeys.CurrentState.STATE_PLAYING:
                    //正在播放
                    break;
                case ConstantKeys.CurrentState.STATE_PAUSED:
                    //暂停播放
                    break;
                case ConstantKeys.CurrentState.STATE_BUFFERING_PAUSED:
                    //暂停缓冲
                    break;
                case ConstantKeys.CurrentState.STATE_COMPLETED:
                    //播放完成
                    break;
            }
        }
    });
    

06.在列表中播放

  • 第一步:初始化视频播放器,创建VideoPlayer对象
    mVideoView = new VideoPlayer(context);
    mVideoView.setOnStateChangeListener(new VideoPlayer.SimpleOnStateChangeListener() {
        @Override
        public void onPlayStateChanged(int playState) {
            //监听VideoViewManager释放,重置状态
            if (playState == ConstantKeys.CurrentState.STATE_IDLE) {
                PlayerUtils.removeViewFormParent(mVideoView);
                mLastPos = mCurPos;
                mCurPos = -1;
            }
        }
    });
    mController = new BasisVideoController(context);
    mVideoView.setController(mController);
    
  • 第二步:设置RecyclerView和Adapter
    mAdapter.setOnItemChildClickListener(new OnItemChildClickListener() {
        @Override
        public void onItemChildClick(int position) {
            //点击item播放视频
            startPlay(position);
        }
    });
    mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(@NonNull View view) {
    
        }
    
        @Override
        public void onChildViewDetachedFromWindow(@NonNull View view) {
            FrameLayout playerContainer = view.findViewById(R.id.player_container);
            View v = playerContainer.getChildAt(0);
            if (v != null && v == mVideoView && !mVideoView.isFullScreen()) {
                //销毁视频
                releaseVideoView();
            }
        }
    });
    
  • 第三步:播放视频和销毁视频的逻辑代码
    /**
     * 开始播放
     * @param position 列表位置
     */
    protected void startPlay(int position) {
        if (mCurPos == position) return;
        if (mCurPos != -1) {
            releaseVideoView();
        }
        VideoInfoBean videoBean = mVideos.get(position);
        mVideoView.setUrl(videoBean.getVideoUrl());
        View itemView = mLinearLayoutManager.findViewByPosition(position);
        if (itemView == null) return;
        VideoRecyclerViewAdapter.VideoHolder viewHolder = (VideoRecyclerViewAdapter.VideoHolder) itemView.getTag();
        //把列表中预置的PrepareView添加到控制器中,注意isPrivate此处只能为true。
        mController.addControlComponent(viewHolder.mPrepareView, true);
        PlayerUtils.removeViewFormParent(mVideoView);
        viewHolder.mPlayerContainer.addView(mVideoView, 0);
        //播放之前将VideoView添加到VideoViewManager以便在别的页面也能操作它
        VideoViewManager.instance().add(mVideoView, "list");
        mVideoView.start();
        mCurPos = position;
    }
    
    private void releaseVideoView() {
        mVideoView.release();
        if (mVideoView.isFullScreen()) {
            mVideoView.stopFullScreen();
        }
        if(getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        mCurPos = -1;
    }
    

08.其他重要功能Api

  • 设置视频播放器背景图,和视频标题。
    //注意,下面这个controller是指BasisVideoController
    //设置视频背景图
    ImageView thumb = controller.getThumb();
    Glide.with(this).load(R.drawable.image_default).into(controller.getThumb());
    //设置视频标题
    controller.setTitle("视频标题");
    
  • 判断是否锁屏
    //判断是否锁屏
    boolean locked = controller.isLocked();
    //设置是否锁屏
    controller.setLocked(true);
    
  • 设置播放视频缩放类型。借鉴于网络博客,类似图片缩放。建议选择16:9类型,最常见
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_16_9);
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_DEFAULT);
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_4_3);
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_MATCH_PARENT);
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_ORIGINAL);
    mVideoPlayer.setScreenScaleType(ConstantKeys.PlayerScreenScaleType.SCREEN_SCALE_CENTER_CROP);
    

09.播放多个视频

  • 这个举一个例子,比如同时播放两个视频,当然这种情况在app中可能比较少
    //必须设置
    player1.setUrl(VOD_URL_1);
    VideoPlayerBuilder.Builder builder = VideoPlayerBuilder.newBuilder();
    builder.setEnableAudioFocus(false);
    VideoPlayerBuilder videoPlayerBuilder = new VideoPlayerBuilder(builder);
    player1.setVideoBuilder(videoPlayerBuilder);
    BasisVideoController controller1 = new BasisVideoController(this);
    player1.setController(controller1);
    mVideoViews.add(player1);
    
    //必须设置
    player2.setUrl(VOD_URL_2);
    VideoPlayerBuilder.Builder builder2 = VideoPlayerBuilder.newBuilder();
    builder.setEnableAudioFocus(false);
    VideoPlayerBuilder videoPlayerBuilder2 = new VideoPlayerBuilder(builder2);
    player2.setVideoBuilder(videoPlayerBuilder2);
    BasisVideoController controller2 = new BasisVideoController(this);
    player2.setController(controller2);
    mVideoViews.add(player2);
    
  • 那么要是页面切换到后台,如何处理多个视频的暂停功能呢?如下所示:
    @Override
    protected void onPause() {
        super.onPause();
        for (VideoPlayer vv : mVideoViews) {
            vv.pause();
        }
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        for (VideoPlayer vv : mVideoViews) {
            vv.pause();
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        for (VideoPlayer vv : mVideoViews) {
            vv.release();
        }
    }
    
    @Override
    public void onBackPressed() {
        for (VideoPlayer vv : mVideoViews) {
            if (vv.onBackPressed())
                return;
        }
        super.onBackPressed();
    }
    

10.VideoPlayer相关Api

  • 关于视频播放相关的api如下所示
    //暂停播放
    mVideoPlayer.pause();
    //视频缓冲完毕,准备开始播放时回调
    mVideoPlayer.onPrepared();
    //重新播放
    mVideoPlayer.replay(true);
    //继续播放
    mVideoPlayer.resume();
    //调整播放进度
    mVideoPlayer.seekTo(100);
    //循环播放, 默认不循环播放
    mVideoPlayer.setLooping(true);
    //设置播放速度
    mVideoPlayer.setSpeed(1.1f);
    //设置音量 0.0f-1.0f 之间
    mVideoPlayer.setVolume(1,1);
    //开始播放
    mVideoPlayer.start();
    
  • 关于视频切换播放模式相关api
    //判断是否处于全屏状态
    boolean fullScreen = mVideoPlayer.isFullScreen();
    //是否是小窗口模式
    boolean tinyScreen = mVideoPlayer.isTinyScreen();
    //进入全屏
    mVideoPlayer.startFullScreen();
    //退出全屏
    mVideoPlayer.stopFullScreen();
    //开启小屏
    mVideoPlayer.startTinyScreen();
    //退出小屏
    mVideoPlayer.stopTinyScreen();
    
  • 关于其他比如获取速度,音量,设置属性相关Api
    //VideoPlayer相关
    VideoPlayerBuilder.Builder builder = VideoPlayerBuilder.newBuilder();
    VideoPlayerBuilder videoPlayerBuilder = new VideoPlayerBuilder(builder);
    //设置视频播放器的背景色
    builder.setPlayerBackgroundColor(Color.BLACK);
    //设置小屏的宽高
    int[] mTinyScreenSize = {0, 0};
    builder.setTinyScreenSize(mTinyScreenSize);
    //是否开启AudioFocus监听, 默认开启
    builder.setEnableAudioFocus(false);
    mVideoPlayer.setVideoBuilder(videoPlayerBuilder);
    //截图
    Bitmap bitmap = mVideoPlayer.doScreenShot();
    //移除所有播放状态监听
    mVideoPlayer.clearOnStateChangeListeners();
    //获取当前缓冲百分比
    int bufferedPercentage = mVideoPlayer.getBufferedPercentage();
    //获取当前播放器的状态
    int currentPlayerState = mVideoPlayer.getCurrentPlayerState();
    //获取当前的播放状态
    int currentPlayState = mVideoPlayer.getCurrentPlayState();
    //获取当前播放的位置
    long currentPosition = mVideoPlayer.getCurrentPosition();
    //获取视频总时长
    long duration = mVideoPlayer.getDuration();
    //获取倍速速度
    float speed = mVideoPlayer.getSpeed();
    //获取缓冲速度
    long tcpSpeed = mVideoPlayer.getTcpSpeed();
    //获取视频宽高
    int[] videoSize = mVideoPlayer.getVideoSize();
    //是否处于静音状态
    boolean mute = mVideoPlayer.isMute();
    

11.Controller相关Api

  • Controller控制器相关的Api说明
    //设置视频背景图
    ImageView thumb = controller.getThumb();
    Glide.with(this).load(R.drawable.image_default).into(controller.getThumb());
    //设置视频标题
    controller.setTitle("视频标题");
    //添加自定义视图。每添加一个视图,都是方式层级树的最上层
    CustomErrorView customErrorView = new CustomErrorView(this);
    controller.addControlComponent(customErrorView);
    //移除控制组件
    controller.removeControlComponent(customErrorView);
    //移除所有的组件
    controller.removeAllControlComponent();
    //隐藏播放视图
    controller.hide();
    //显示播放视图
    controller.show();
    //是否开启根据屏幕方向进入/退出全屏
    controller.setEnableOrientation(true);
    //显示移动网络播放提示
    controller.showNetWarning();
    //刘海的高度
    int cutoutHeight = controller.getCutoutHeight();
    //是否有刘海屏
    boolean b = controller.hasCutout();
    //设置是否适配刘海屏
    controller.setAdaptCutout(true);
    //停止刷新进度
    controller.stopProgress();
    //开始刷新进度,注意:需在STATE_PLAYING时调用才会开始刷新进度
    controller.startProgress();
    //判断是否锁屏
    boolean locked = controller.isLocked();
    //设置是否锁屏
    controller.setLocked(true);
    //取消计时
    controller.stopFadeOut();
    //开始计时
    controller.startFadeOut();
    //设置播放视图自动隐藏超时
    controller.setDismissTimeout(8);
    //销毁
    controller.destroy();
    

12.边播放边缓存api

  • 如下所示
    controller = new BasisVideoController(this);
    //设置视频背景图
    Glide.with(this).load(R.drawable.image_default).into(controller.getThumb());
    //设置控制器
    mVideoPlayer.setController(controller);
    HttpProxyCacheServer server = new HttpProxyCacheServer(this);
    String proxyVideoUrl = server.getProxyUrl(URL_AD);
    mVideoPlayer.setUrl(proxyUrl);
    mVideoPlayer.start();
    

13.类似抖音视频预加载

  • 如下所示,这个是针对ViewPager
    //获取PreloadManager预加载管理者对象
    mPreloadManager = PreloadManager.getInstance(this);
    //在播放视频的时候
    String playUrl = mPreloadManager.getPlayUrl(url);
    VideoLogUtils.i("startPlay: " + "position: " + position + "  url: " + playUrl);
    mVideoPlayer.setUrl(playUrl);
    //在页面滚动的时候
    mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageScrollStateChanged(int state) {
            super.onPageScrollStateChanged(state);
            if (state == VerticalViewPager.SCROLL_STATE_IDLE) {
                mPreloadManager.resumePreload(mCurPos, mIsReverseScroll);
            } else {
                mPreloadManager.pausePreload(mCurPos, mIsReverseScroll);
            }
        }
    });
    
  • 如下所示,这个是针对RecyclerView
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        /**
         * 是否反向滑动
         */
        private boolean mIsReverseScroll;
    
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy>0){
                //表示下滑
                mIsReverseScroll = false;
            } else {
                //表示上滑
                mIsReverseScroll = true;
            }
        }
    
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == VerticalViewPager.SCROLL_STATE_IDLE) {
                mPreloadManager.resumePreload(mCurPos, mIsReverseScroll);
            } else {
                mPreloadManager.pausePreload(mCurPos, mIsReverseScroll);
            }
        }
    });
    

14.视频播放器埋点

  • 代码如下所示,写一个类,实现BuriedPointEvent即可。即可埋点视频的播放次数,播放进度,点击视频广告啥的,方便统一管理
public class BuriedPointEventImpl implements BuriedPointEvent {

    /**
     * 进入视频播放
     * @param url                       视频url
     */
    @Override
    public void playerIn(String url) {

    }
    
    /**
     * 退出视频播放
     * @param url                       视频url
     */
    @Override
    public void playerDestroy(String url) {

    }

    /**
     * 视频播放完成
     * @param url                       视频url
     */
    @Override
    public void playerCompletion(String url) {

    }

    /**
     * 视频播放异常
     * @param url                       视频url
     * @param isNetError                是否是网络异常
     */
    @Override
    public void onError(String url, boolean isNetError) {

    }

    /**
     * 点击了视频广告
     * @param url                       视频url
     */
    @Override
    public void clickAd(String url) {

    }

    /**
     * 退出视频播放时候的播放进度百度分
     * @param url                       视频url
     * @param progress                  视频进度,计算百分比【退出时候进度 / 总进度】
     */
    @Override
    public void playerOutProgress(String url, float progress) {

    }

    /**
     * 视频切换音频
     * @param url                       视频url
     */
    @Override
    public void videoToMedia(String url) {

    }
}

15.播放器示例展示图

image

image

image

image

image

image

image

image

image

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