從零開始打造一個VR視頻播放器-VRPlayer源碼分析

  • 該文章已于2017-8-3被gank.io推薦
  • 該文章已授權(quán)任玉剛公眾號[玉剛說]發(fā)表

個人小站

Github

簡書

項目地址

簡介

VRPlayer是一個本地VR視頻播放器誉简,整體使用了 DataBinding碉就,MVVM架構(gòu),播放部分基于IJKPlayer描融,VR渲染部分基于MD360Player4Android,UI上部分使用了Carbon铝噩,沉浸式狀態(tài)欄使用了 StatusBarUtil這個項目,圖片加載使用 Glide
VRPlayer會掃描你手機(jī)中的視頻文件窿克,然后你可以找到你要播放的VR視頻文件骏庸,點(diǎn)擊即可播放

效果


分析

項目主要分三部分,一是重寫的MediaController,二就是播放器的包裝類年叮,也相當(dāng)于我們的原生的VideoView,源碼中為PlayerView,最后一部分將PlayerView和MD360Player庫中的VRLibrary整合包裝具被,也就是源碼中的VRPlayerView,實(shí)現(xiàn)VR模式控制的接口只损,在使用的時候就只需要添加這一個View
本文主要分析拓展的部分一姿,因為并不是VideoView,MediaController,或是IJk Demo的分析,所以重合部分就不作分析了跃惫,如果同學(xué)們對這一部分不熟悉叮叹,可以自行學(xué)習(xí),
PlayerView的包裝可以參考原生VideoView以及IJkPlayer的Demo爆存,
MediaController可參考原生代碼蛉顽,
VRPlayerView可以參考MD360Player的Demo

播放部分根據(jù)IJkPlayer的Demo進(jìn)行修改,重寫MediaController先较,IJKPlayer的Demo也是根據(jù)原生的VideoView進(jìn)行修改携冤,但并沒有自定義MediaController,原生的VideoView+MediaController想必做過視頻播放的同學(xué)都比較熟悉了

mVideoView.setMediaController(mMediaController);
mMediaController.setMediaPlayer(mVideoView);

優(yōu)點(diǎn)在于播放和控制解耦闲勺,但是原生的MediaController類可定制性很低曾棕,創(chuàng)建View的方法都是私有,并且用到了PhoneWindow這種系統(tǒng)內(nèi)部才開放的類
創(chuàng)建Window時使用了PhoneWindow

mWindow = new PhoneWindow(mContext);

初始化Controller的方法私有

private void initControllerView(View v) {
}

所以通過繼承來自定義是不可能的菜循,我看到的好多開源項目都是將播放以及控制寫到一個View中翘地,但是我并不認(rèn)為這是一種優(yōu)雅的方式,所以我們得自己來重寫MediaController

重寫MediaController

先把MediaController復(fù)制粘貼一份,所以重合部分請參考原生MediaController以及源碼中的VRMediaController
下邊根據(jù)幾個關(guān)鍵點(diǎn)給大家講解分析

將Window改為PopWindow

  • 定義
private PopupWindow mWindow;
  • 初始化
private void initFloatingWindow() {
    mWindow = new PopupWindow(mContext);
    mWindow.setFocusable(false);
    mWindow.setBackgroundDrawable(null);
    mWindow.setOutsideTouchable(true);
    mAnimStyle = android.R.style.Animation;
    requestFocus();
    }
  • 然后在setAnchorView(View view)方法中子眶,把Controller的View放到PopWindow中,makeControllerView()返回的是Controller的View瀑凝,下邊會進(jìn)行講解
    public void setAnchorView(View view) {
        mAnchor = view;
        if (!mFromXml) {
            removeAllViews();
            mRoot = makeControllerView();
            mWindow.setContentView(mRoot);
            mWindow.setWidth(LayoutParams.MATCH_PARENT);
            mWindow.setHeight(LayoutParams.WRAP_CONTENT);
        }
        initControllerView(mRoot);
    }

下邊講解Controller的創(chuàng)建

Controller的創(chuàng)建

  • 在makeControllerView()方法中生成Controller的View
    protected View makeControllerView() {
        return ((LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("media_controller_vr", "layout", mContext.getPackageName()), this);
    }

這里我們?nèi)〉肰iew的方式參考原生,使用getSystemService的方式獲取臭杰,如果同學(xué)覺得不好粤咪,可以使用常規(guī)方式獲取,原生采用這樣的方式獲取渴杆,我猜是為了防止包名變化寥枝,獲取不到View

  • View
    在源碼中對應(yīng)media_controller_vr.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="50dp"
              android:background="@color/mediacontroller_bg"
              android:gravity="center"
              android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <ImageView
            android:id="@+id/mediacontroller_play_pause"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:contentDescription="Play/Pause"
            android:src="@drawable/mediacontroller_pause" />

        <TextView
            android:id="@+id/mediacontroller_time_current"
            style="@style/MediaController_Text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="5dp"
            android:layout_toRightOf="@id/mediacontroller_play_pause" />

        <TextView
            android:id="@+id/mediacontroller_time_total"
            style="@style/MediaController_Text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/mediacontroller_handlerLayout"
            android:layout_marginRight="5dp" />

        <android.support.v7.widget.AppCompatSeekBar
            android:id="@+id/mediacontroller_seekbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/mediacontroller_time_total"
            android:layout_toRightOf="@id/mediacontroller_time_current"
            android:focusable="true"
            android:paddingLeft="5dp"
            android:paddingRight="5dp"
            android:thumbTint="#33cc99"
            android:progressTint="#FFFFFF"
            android:max="1000" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:id="@+id/mediacontroller_handlerLayout"
            android:gravity="center"
            android:layout_margin="5dp"
            android:orientation="horizontal">
            <ImageView
                android:id="@+id/mediacontroller_interactive"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:src="@drawable/ic_touch_mode"
                />

            <ImageView
                android:id="@+id/mediacontroller_display"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="10dp"
                android:src="@drawable/ic_eye_mode"
                />

        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

效果是這樣的

最后兩個ImageView是我們VR控制的部分,后邊要編寫對應(yīng)的接口

  • 初始化Controller
    初始化Controller和原生的并并無二致磁奖,重點(diǎn)是要設(shè)置相應(yīng)的監(jiān)聽
private void initControllerView(View v) {
        mPauseButton = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_play_pause", "id", mContext.getPackageName()));
        if (mPauseButton != null) {
            mPauseButton.requestFocus();
            mPauseButton.setOnClickListener(mPauseListener);
        }

        mVRInteractiveModeButton = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_interactive", "id", mContext.getPackageName()));
        if (mVRInteractiveModeButton != null) {
            mVRInteractiveModeButton.requestFocus();
            mVRInteractiveModeButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mVRControl.onInteractiveClick(interactiveMode);
                    updateInteractive();
                }
            });
        }
        mVRDisplayModeButton = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_display", "id", mContext.getPackageName()));
        if (mVRDisplayModeButton != null) {
            mVRDisplayModeButton.requestFocus();
            mVRDisplayModeButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mVRControl.onDisplayClick(displayMode);
                    updateDisplay();
                }
            });
        }

        mProgress = (SeekBar) v.findViewById(getResources().getIdentifier("mediacontroller_seekbar", "id", mContext.getPackageName()));
        if (mProgress != null) {
            if (mProgress instanceof SeekBar) {
                SeekBar seeker = (SeekBar) mProgress;
                seeker.setOnSeekBarChangeListener(mSeekListener);
            }
            mProgress.setMax(1000);
        }

        mEndTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_total", "id", mContext.getPackageName()));
        mCurrentTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_current", "id", mContext.getPackageName()));

        mFormatBuilder = new StringBuilder();
        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
    }

這樣囊拜,Controller 的View的創(chuàng)建過程就完成了

Progress、show比搭、hide

  • 自定義Handler更新Progress冠跷,show、hide View
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            long pos;
            switch (msg.what) {
                case FADE_OUT:
                    hide();
                    break;
                case SHOW_PROGRESS:
                    pos = setProgress();
                    if (!mDragging && mShowing) {
                        msg = obtainMessage(SHOW_PROGRESS);
                        sendMessageDelayed(msg, 1000 - (pos % 1000));
                        updatePausePlay();
                    }
                    break;
            }
        }
    };

因為刷新View的方式改為了自定義的Handler身诺,所以相應(yīng)部分的代碼要進(jìn)行修改蜜托,具體情參見源碼
并且因為Window改為了PopupWindow,在show霉赡、hide時候橄务,要將

mWindowManager.addView(mDecor, mDecorLayoutParams);
mWindowManager.removeView(mDecor);

改為直接操作View,即

setVisibility(View.VISIBLE);
setVisibility(View.GONE);

擴(kuò)展

VRMediaController主要拓展了VR播放模式的控制,以及在視頻上方添加一個可定制的Title的功能

  • VR播放模式控制
    播放模式主要是結(jié)合MD360Player庫使用穴亏,提供回調(diào)接口蜂挪,這里我使用了interactiveMode的INTERACTIVE_MODE_CARDBORAD_MOTION、INTERACTIVE_MODE_TOUCH嗓化,主要是切換播放時的畫面控制棠涮,陀螺儀控制以及觸摸控制模式;DisplayMode的MDVRLibrary.DISPLAY_MODE_GLASS模式以及MDVRLibrary.DISPLAY_MODE_NORMAL模式刺覆,GLASS模式播放雙目的視頻故爵,我們可以將手機(jī)放到VR盒子里進(jìn)行觀看,NORMAL播放單目視頻
    我們已經(jīng)在Controller布局文件里定義了兩個按鈕來切換這兩種模式隅津,現(xiàn)在在Controller中編寫接口
    public interface VRControl {
        void onInteractiveClick(int currentMode);
        void onDisplayClick(int currentMode);
    }

并且在點(diǎn)擊時,切換按鈕的圖標(biāo)

    private void updateInteractive() {
        if (mRoot == null || mVRInteractiveModeButton == null)
            return;
        if (interactiveMode == VR_INTERACTIVE_MODE_GYROSCOPE) {
            interactiveMode = VR_INTERACTIVE_MODE_TOUCH;
            mVRInteractiveModeButton.setImageResource(getResources().getIdentifier("ic_gyroscope", "drawable", mContext.getPackageName()));
        }
        else {
            interactiveMode = VR_INTERACTIVE_MODE_GYROSCOPE;
            mVRInteractiveModeButton.setImageResource(getResources().getIdentifier("ic_touch_mode", "drawable", mContext.getPackageName()));
        }
    }

    private void updateDisplay() {
        if (mRoot == null || mVRDisplayModeButton == null)
            return;
        if (displayMode == VR_DISPLAY_MODE_GLASS) {
            displayMode = VR_DISPLAY_MODE_NORMAL;
            mVRDisplayModeButton.setImageResource(getResources().getIdentifier("ic_vr_mode", "drawable", mContext.getPackageName()));
        }
        else {
            displayMode = VR_DISPLAY_MODE_GLASS;
            mVRDisplayModeButton.setImageResource(getResources().getIdentifier("ic_eye_mode", "drawable", mContext.getPackageName()));
        }
    }
  • Title
    Title主要是實(shí)現(xiàn)在視頻的上方添加一個自定義的Title劲室,實(shí)現(xiàn)返回等功能的實(shí)現(xiàn)伦仍,并且和Controller一起show、hide,
    TitleView就是一個普通的View,在使用VRPlayerView時候定義
 <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.wheat7.vrplayer.vr.VRPlayerView
        android:id="@+id/player"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


        <RelativeLayout
            android:id="@+id/mediacontroller_title"
            android:layout_width="match_parent"
            android:layout_height="33dp"
            android:background="@color/mediacontroller_bg"
            android:visibility="gone">

            <carbon.widget.ImageView
                android:layout_marginTop="3dp"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:clickable="true"
                android:onClick="@{()-> activity.onBackClick()}"
                android:src="@drawable/ic_back" />
        </RelativeLayout>
    </FrameLayout>
    

然后通過setTitleView(View v)方法傳入Controller很洋,并在show()充蓝、hide()方法中和Controller一同show、hide就可以了

PlayerView包裝

PlayerView就類似于原生的VideoView,準(zhǔn)確的說谓苟,就是從ViedoView修改過來的官脓,其就是一個MediaPlayer的包裝類,與Mediaplayer結(jié)合涝焙,實(shí)現(xiàn)各種控制的回調(diào)卑笨,不了解的同學(xué)可以參考VideoView源碼和 IJKPlayer的 Demo,不同的是仑撞,VideoView直接繼承了SurfaceView作為播放顯示的View赤兴,我們這里做了修改,繼承了FrameLayout隧哮,因為播放VR視頻使用的是MD360Player的OpenGl庫中提供的GLSurfaceView桶良,并通過Media的setSurfacefan方法進(jìn)行設(shè)置,但是在PlayerVie中還是以addView的方式添加SurfaceView沮翔,可以通過setSurfaceView(SurfaceView surfaceView)方法傳入陨帆,以便擴(kuò)展,下邊主要講解擴(kuò)展部分

  • 擴(kuò)展
    擴(kuò)展部分主要是添加了IJKPlayer的硬解碼功能采蚀,這也是MD360Player的Demo中添加的疲牵,如果加入硬解,播放會很卡頓搏存,并且發(fā)熱量很大
    private void enableHardwareDecoding(){
        if (mMediaPlayer instanceof IjkMediaPlayer){
            IjkMediaPlayer player = (IjkMediaPlayer) mMediaPlayer;
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 60);
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-fps", 0);
            player.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
        }
    }
    

然后在mMediaPlayer創(chuàng)建以后調(diào)用enableHardwareDecoding()即可

VRPlayerView-PlayerView與VRLibrary整合

VRPlayerView主要是將PlayerViewGLSurfaceView進(jìn)行包裝瑰步,并與MDVRLibraryj進(jìn)行整合,實(shí)現(xiàn)VRMediaController.VRControl接口璧眠,控制VR播放模式缩焦,在使用時,在布局中添加一個VRPlayerView即可

  • VRPlayerView繼承于FrameLayout责静,初始化時候袁滥,依次add GLSurfaceView、PlayerView灾螃,并且設(shè)置MediaController
 private void init() {
        setKeepScreenOn(true);
        mGLSurfaceView=new GLSurfaceView(getContext());
        addView(mGLSurfaceView);
        mPlayerView = new PlayerView(getContext());
        addView(mPlayerView);
        mMediaController = new VRMediaController(getContext());
        mPlayerView.setMediaController(mMediaController);
        mMediaController.setMediaPlayer(mPlayerView);
        mMediaController.setOnVRControlListener(this);
        initVRLibrary();
    }
  • 初始化VRLibrary
    MD360Player有很多使用方法题翻,是個強(qiáng)大的庫,具體使用可以參見該項目,下邊初始化我們用到的
private void initVRLibrary() {
        // new instance
        mVRLibrary = MDVRLibrary.with(getContext())
                .displayMode(MDVRLibrary.DISPLAY_MODE_GLASS)
                .interactiveMode(MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION)
                .projectionMode(MDVRLibrary.PROJECTION_MODE_SPHERE)
                .pinchConfig(new MDPinchConfig().setDefaultValue(0.7f).setMin(0.5f))
                .pinchEnabled(true)
                .directorFactory(new MD360DirectorFactory() {
                    @Override
                    public MD360Director createDirector(int index) {
                        return MD360Director.builder().setPitch(90).build();
                    }
                })
                .asVideo(new MDVRLibrary.IOnSurfaceReadyCallback() {
                    @Override
                    public void onSurfaceReady(Surface surface) {
                        // IjkMediaPlayer or MediaPlayer
                        mPlayerView.getPlayer().setSurface(surface);
                    }
                })
                .build(mGLSurfaceView);
        mVRLibrary.setAntiDistortionEnabled(true);
    }

  • 實(shí)現(xiàn)VRMediaController.VRControl接口
    @Override
    public void onInteractiveClick(int currentMode) {
        if (currentMode == MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION) {
            mVRLibrary.switchInteractiveMode(getContext(), MDVRLibrary.INTERACTIVE_MODE_TOUCH);
        } else {
            mVRLibrary.switchInteractiveMode(getContext(), MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION);
        }
    }

    @Override
    public void onDisplayClick(int currentMode) {
        if (currentMode == MDVRLibrary.DISPLAY_MODE_GLASS) {
            mVRLibrary.switchDisplayMode(getContext(), MDVRLibrary.DISPLAY_MODE_NORMAL);
            mVRLibrary.setAntiDistortionEnabled(false);
        } else {
            mVRLibrary.switchDisplayMode(getContext(), MDVRLibrary.DISPLAY_MODE_GLASS);
            mVRLibrary.setAntiDistortionEnabled(true);
        }
    }
  • 包裝一些方法方便調(diào)用
  public AbstractMediaPlayer getMediaPlayer() {
        return mPlayerView.getPlayer();
    }

    public PlayerView getPlayerView() {
        return mPlayerView;
    }

    public void setVideoPath(String path) {
        mPlayerView.setVideoPath(path);
    }

    public void setVideoUri(Uri uri) {
        mPlayerView.setVideoURI(uri);
    }

    public void setMediaControllerTitle(View v) {
        mMediaController.setTitleView(v);
    }
  • 生命周期控制
    生命周期控制是必須的腰鬼,具體可以參考MD360Player
   public void onPause(){
        if (mVRLibrary!=null)mVRLibrary.onPause(getContext());
        if (mPlayerView!=null)mPlayerView.pause();
    }
    public void onResume(){
        if (mVRLibrary!=null)
            mVRLibrary.onResume(getContext());
        if (mPlayerView!=null)
            mPlayerView.resume();
    }
    public void onDestroy(){
        if (mVRLibrary!=null) mVRLibrary.onDestroy();
        if (mPlayerView!=null) mPlayerView.stopPlayback();
    }

使用

  • 在使用的時候嵌赠,在布局文件中添加VRPlayerView以及TitleView
 <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.wheat7.vrplayer.vr.VRPlayerView
        android:id="@+id/player"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


        <RelativeLayout
            android:id="@+id/mediacontroller_title"
            android:layout_width="match_parent"
            android:layout_height="33dp"
            android:background="@color/mediacontroller_bg"
            android:visibility="gone">

            <carbon.widget.ImageView
                android:layout_marginTop="3dp"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:clickable="true"
                android:onClick="@{()-> activity.onBackClick()}"
                android:src="@drawable/ic_back" />
        </RelativeLayout>

    </FrameLayout>
  • 初始化 Databinding方式
getBinding().player.setVideoPath(urlStr);
        getBinding().player.setMediaControllerTitle(getBinding().mediacontrollerTitle);
  • 生命周期控制
    @Override
    protected void onPause() {
        super.onPause();
        getBinding().player.onPause();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getBinding().player.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        getBinding().player.onResume();
    }

題外話

Databinding BaseActivity封裝

對于Databinding的使用,相信同學(xué)們已經(jīng)非常熟悉了熄赡,現(xiàn)在分享一種Databinding的BaseActivity的封裝方式

public abstract class BaseActivity<T extends ViewDataBinding> extends AppCompatActivity {

    private View mainView;
    private ViewDataBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        int layoutId = getLayoutId();
        super.onCreate(savedInstanceState);
        try {
            binding = DataBindingUtil.setContentView(this, layoutId);
            if (binding != null) {
                mainView = binding.getRoot();
            } else {
                mainView = LayoutInflater.from(this).inflate(layoutId, null);
                setContentView(mainView);
            }

        } catch (NoClassDefFoundError e) {
            mainView = LayoutInflater.from(this).inflate(layoutId, null);
            setContentView(mainView);
        }
        initView(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }


    public T getBinding() {
        return (T) binding;
    }

    public abstract int getLayoutId();

    public abstract void initView(Bundle savedInstanceState);

}

通過泛型參數(shù)將相應(yīng)Binding類傳入姜挺,然后就可以通過getBinding()方法獲取對應(yīng)的Binding類热康,通過getLayoutId()方法傳入布局耐版,在使用時在initView()中初始化Activity

其他

項目還包括一些其他的東西兽愤,包括Databinding的ViewHolder、歡迎界面的閃動TextView饼丘、沉浸式狀態(tài)欄工具類StatusBarUtil的使用等蠢壹,就不作贅述了猫妙,詳見源碼喧务,如果有要和我討論的同學(xué),可以聯(lián)系我哦

最后

最后懇請同學(xué)們不吝惜的給一個Star缺虐,這對于我很重要芜壁!謝謝!Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末志笼,一起剝皮案震驚了整個濱河市沿盅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纫溃,老刑警劉巖腰涧,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異紊浩,居然都是意外死亡窖铡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門坊谁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來费彼,“玉大人,你說我怎么就攤上這事口芍」坎” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵鬓椭,是天一觀的道長颠猴。 經(jīng)常有香客問我,道長小染,這世上最難降的妖魔是什么翘瓮? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮裤翩,結(jié)果婚禮上资盅,老公的妹妹穿的比我還像新娘。我一直安慰自己踊赠,他們只是感情好呵扛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筐带,像睡著了一般今穿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烫堤,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼鸽斟。 笑死拔创,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的富蓄。 我是一名探鬼主播剩燥,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼立倍!你這毒婦竟也來了灭红?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤口注,失蹤者是張志新(化名)和其女友劉穎变擒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寝志,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娇斑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了材部。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫缆。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乐导,靈堂內(nèi)的尸體忽然破棺而出苦丁,到底是詐尸還是另有隱情,我是刑警寧澤物臂,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布旺拉,位于F島的核電站,受9級特大地震影響鹦聪,放射性物質(zhì)發(fā)生泄漏账阻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一泽本、第九天 我趴在偏房一處隱蔽的房頂上張望淘太。 院中可真熱鬧,春花似錦规丽、人聲如沸蒲牧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冰抢。三九已至,卻和暖如春艘狭,著一層夾襖步出監(jiān)牢的瞬間挎扰,已是汗流浹背翠订。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遵倦,地道東北人尽超。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像梧躺,于是被迫代替她去往敵國和親似谁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容