分享:iOS音頻播放系列之AudioSession

本篇為《iOS音頻播放》系列的第二篇巷嚣。

在實(shí)施前一篇中所述的7個(gè)步驟之前還必須面對(duì)一個(gè)麻煩的問題荒吏,AudioSession。

本篇主要介紹關(guān)于AudioSession使用、期間需要注意的地方以及可能面臨的坑。原文|地址

AudioSession簡(jiǎn)介

AudioSession這個(gè)玩意的主要功能包括以下幾點(diǎn)(圖片來自官方文檔):

  1. 確定你的app如何使用音頻(是播放?還是錄音?)
  2. 為你的app選擇合適的輸入輸出設(shè)備(比如輸入用的麥克風(fēng)酌摇,輸出是耳機(jī)、手機(jī)功放或者airplay)
  3. 協(xié)調(diào)你的app的音頻播放和系統(tǒng)以及其他app行為(例如有電話時(shí)需要打斷嗡载,電話結(jié)束時(shí)需要恢復(fù)窑多,按下靜音按鈕時(shí)是否歌曲也要靜音等)


    AudioSession

    AudioSession相關(guān)的類有兩個(gè):

1.AudioToolBox中的AudioSession
2.AVFoundation中的AVAudioSession

其中AudioSession在SDK 7中已經(jīng)被標(biāo)注為depracated,而AVAudioSession這個(gè)類雖然iOS 3開始就已經(jīng)存在了洼滚,但其中很多方法和變量都是在iOS 6以后甚至是iOS 7才有的埂息。所以各位可以依照以下標(biāo)準(zhǔn)選擇:

  • 如果最低版本支持iOS 5,可以使用AudioSession遥巴,也可以使用AVAudioSession千康;
  • 如果最低版本支持iOS 6及以上,請(qǐng)使用AVAudioSession

下面以AudioSession類為例來講述AudioSession相關(guān)功能的使用(很不幸我需要支持iOS 5铲掐。拾弃。T-T,使用AVAudioSession的同學(xué)可以在其頭文件中尋找對(duì)應(yīng)的方法使用即可摆霉,需要注意的點(diǎn)我會(huì)加以說明).

注意:在使用AVAudioPlayer/AVPlayer時(shí)可以不用關(guān)心AudioSession的相關(guān)問題豪椿,Apple已經(jīng)把AudioSession的處理過程封裝了,但音樂打斷后的響應(yīng)還是要做的(比如打斷后音樂暫停了UI狀態(tài)也要變化携栋,這個(gè)應(yīng)該通過KVO就可以搞定了吧搭盾。。我沒試過瞎猜的>_<)婉支。

注意:在使用MPMusicPlayerController時(shí)不必關(guān)心AudioSession的問題鸯隅。

初始化AudioSession

使用AudioSession類首先需要調(diào)用初始化方法:

extern OSStatus AudioSessionInitialize(CFRunLoopRef inRunLoop,
                                       CFStringRef inRunLoopMode,
                                       AudioSessionInterruptionListener inInterruptionListener,
                                       void *inClientData);

前兩個(gè)參數(shù)一般填NULL表示AudioSession運(yùn)行在主線程上(但并不代表音頻的相關(guān)處理運(yùn)行在主線程上,只是AudioSession)向挖,第三個(gè)參數(shù)需要傳入一個(gè)AudioSessionInterruptionListener類型的方法滋迈,作為AudioSession被打斷時(shí)的回調(diào)霎奢,第四個(gè)參數(shù)則是代表打斷回調(diào)時(shí)需要附帶的對(duì)象(即回到方法中的inClientData,如下所示饼灿,可以理解為UIView animation中的context)。

typedef void (*AudioSessionInterruptionListener)(void * inClientData, UInt32 inInterruptionState);

這才剛開始帝美,坑就來了碍彭。這里會(huì)有兩個(gè)問題:

第一,AudioSessionInitialize可以被多次執(zhí)行悼潭,但AudioSessionInterruptionListener只能被設(shè)置一次庇忌,這就意味著這個(gè)打斷回調(diào)方法是一個(gè)靜態(tài)方法,一旦初始化成功以后所有的打斷都會(huì)回調(diào)到這個(gè)方法舰褪,即便下一次再次調(diào)用AudioSessionInitialize并且把另一個(gè)靜態(tài)方法作為參數(shù)傳入皆疹,當(dāng)打斷到來時(shí)還是會(huì)回調(diào)到第一次設(shè)置的方法上。

這種場(chǎng)景并不少見占拍,例如你的app既需要播放歌曲又需要錄音略就,當(dāng)然你不可能知道用戶會(huì)先調(diào)用哪個(gè)功能,所以你必須在播放和錄音的模塊中都調(diào)用AudioSessionInitialize注冊(cè)打斷方法晃酒,但最終打斷回調(diào)只會(huì)作用在先注冊(cè)的那個(gè)模塊中表牢,很蛋疼吧。贝次。崔兴。所以對(duì)于AudioSession的使用最好的方法是生成一個(gè)類單獨(dú)進(jìn)行管理,統(tǒng)一接收打斷回調(diào)并發(fā)送自定義的打斷通知蛔翅,在需要用到AudioSession的模塊中接收通知并做相應(yīng)的操作敲茄。

Apple也察覺到了這一點(diǎn),所以在AVAudioSession中首先取消了Initialize方法山析,改為了單例方法sharedInstance堰燎。在iOS 5上所有的打斷都需要通過設(shè)置id<AVAudioSessionDelegate> delegate并實(shí)現(xiàn)回調(diào)方法來實(shí)現(xiàn),這同樣會(huì)有上述的問題盖腿,所以在iOS 5使用AVAudioSession下仍然需要一個(gè)單獨(dú)管理AudioSession的類存在爽待。在iOS 6以后Apple終于把打斷改成了通知的形式。翩腐。這下科學(xué)了鸟款。

第二,AudioSessionInitialize方法的第四個(gè)參數(shù)inClientData茂卦,也就是回調(diào)方法的第一個(gè)參數(shù)何什。上面已經(jīng)說了打斷回調(diào)是一個(gè)靜態(tài)方法,而這個(gè)參數(shù)的目的是為了能讓回調(diào)時(shí)拿到context(上下文信息)等龙,所以這個(gè)inClientData需要是一個(gè)有足夠長(zhǎng)生命周期的對(duì)象(當(dāng)然前提是你確實(shí)需要用到這個(gè)參數(shù))处渣,如果這個(gè)對(duì)象被dealloc了伶贰,那么回調(diào)時(shí)拿到的inClientData會(huì)是一個(gè)野指針。就這一點(diǎn)來說構(gòu)造一個(gè)單獨(dú)管理AudioSession的類也是有必要的罐栈,因?yàn)檫@個(gè)類的生命周期和AudioSession一樣長(zhǎng)黍衙,我們可以把context保存在這個(gè)類中。

監(jiān)聽RouteChange事件

如果想要實(shí)現(xiàn)類似于“拔掉耳機(jī)就把歌曲暫蛙埽”的功能就需要監(jiān)聽RouteChange事件:

extern OSStatus AudioSessionAddPropertyListener(AudioSessionPropertyID inID,
                                                AudioSessionPropertyListener inProc,
                                                void *inClientData);
                                              
typedef void (*AudioSessionPropertyListener)(void * inClientData,
                                             AudioSessionPropertyID inID,
                                             UInt32 inDataSize,
                                             const void * inData);

調(diào)用上述方法琅翻,AudioSessionPropertyID參數(shù)傳kAudioSessionProperty_AudioRouteChange,AudioSessionPropertyListener參數(shù)傳對(duì)應(yīng)的回調(diào)方法柑贞。inClientData參數(shù)同AudioSessionInitialize方法方椎。

同樣作為靜態(tài)回調(diào)方法還是需要統(tǒng)一管理,接到回調(diào)時(shí)可以把第一個(gè)參數(shù)inData轉(zhuǎn)換成CFDictionaryRef并從中獲取kAudioSession_AudioRouteChangeKey_Reason鍵值對(duì)應(yīng)的value(應(yīng)該是一個(gè)CFNumberRef)钧嘶,得到這些信息后就可以發(fā)送自定義通知給其他模塊進(jìn)行相應(yīng)操作(例如kAudioSessionRouteChangeReason_OldDeviceUnavailable就可以用來做“拔掉耳機(jī)就把歌曲暫吞闹冢”)。

//AudioSession的AudioRouteChangeReason枚舉
enum {
      kAudioSessionRouteChangeReason_Unknown = 0,
      kAudioSessionRouteChangeReason_NewDeviceAvailable = 1,
      kAudioSessionRouteChangeReason_OldDeviceUnavailable = 2,
      kAudioSessionRouteChangeReason_CategoryChange = 3,
      kAudioSessionRouteChangeReason_Override = 4,
      kAudioSessionRouteChangeReason_WakeFromSleep = 6,
      kAudioSessionRouteChangeReason_NoSuitableRouteForCategory = 7,
      kAudioSessionRouteChangeReason_RouteConfigurationChange = 8
  };
//AVAudioSession的AudioRouteChangeReason枚舉
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
  AVAudioSessionRouteChangeReasonUnknown = 0,
  AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
  AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
  AVAudioSessionRouteChangeReasonCategoryChange = 3,
  AVAudioSessionRouteChangeReasonOverride = 4,
  AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
  AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
  AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
}

注意:iOS 5下如果使用了AVAudioSession由于AVAudioSessionDelegate中并沒有定義相關(guān)的方法有决,還是需要用這個(gè)方法來實(shí)現(xiàn)監(jiān)聽闸拿。iOS 6下直接監(jiān)聽AVAudioSession的通知就可以了。

這里附帶兩個(gè)方法的實(shí)現(xiàn)疮薇,都是基于AudioSession類的(使用AVAudioSession的同學(xué)幫不到你們啦)胸墙。

1、判斷是否插了耳機(jī):

+ (BOOL)usingHeadset
{
#if TARGET_IPHONE_SIMULATOR
    return NO;
#endif

    CFStringRef route;
    UInt32 propertySize = sizeof(CFStringRef);
    AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route);

    BOOL hasHeadset = NO;
    if((route == NULL) || (CFStringGetLength(route) == 0))
    {
        // Silent Mode
    }
    else
    {
        /* Known values of route:
         * "Headset"
         * "Headphone"
         * "Speaker"
         * "SpeakerAndMicrophone"
         * "HeadphonesAndMicrophone"
         * "HeadsetInOut"
         * "ReceiverAndMicrophone"
         * "Lineout"
         */
        NSString* routeStr = (__bridge NSString*)route;
        NSRange headphoneRange = [routeStr rangeOfString : @"Headphone"];
        NSRange headsetRange = [routeStr rangeOfString : @"Headset"];

        if (headphoneRange.location != NSNotFound)
        {
            hasHeadset = YES;
        }
        else if(headsetRange.location != NSNotFound)
        {
            hasHeadset = YES;
        }
    }

    if (route)
    {
        CFRelease(route);
    }

    return hasHeadset;
}

2按咒、判斷是否開了Airplay(來自StackOverflow):

+ (BOOL)isAirplayActived
{
    CFDictionaryRef currentRouteDescriptionDictionary = nil;
    UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);
    AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, &currentRouteDescriptionDictionary);

    BOOL airplayActived = NO;
    if (currentRouteDescriptionDictionary)
    {
        CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
        if(outputs != NULL && CFArrayGetCount(outputs) > 0)
        {
            CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
            //Get the output type (will show airplay / hdmi etc
            CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);

            airplayActived = (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo);
        }
        CFRelease(currentRouteDescriptionDictionary);
    }
    return airplayActived;
}

設(shè)置類別

下一步要設(shè)置AudioSession的Category迟隅,使用AudioSession時(shí)調(diào)用下面的接口

extern OSStatus AudioSessionSetProperty(AudioSessionPropertyID inID,
                                        UInt32 inDataSize,
                                        const void *inData);

如果我需要的功能是播放,執(zhí)行如下代碼

UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory,
                         sizeof(sessionCategory),
                         &sessionCategory);

使用AVAudioSession時(shí)調(diào)用下面的接口

/* set session category */
- (BOOL)setCategory:(NSString *)category error:(NSError **)outError;
/* set session category with options */
- (BOOL)setCategory:(NSString *)category withOptions: (AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0);

至于Category的類型在官方文檔中都有介紹励七,我這里也只羅列一下具體就不贅述了智袭,各位在使用時(shí)可以依照自己需要的功能設(shè)置Category。

//AudioSession的AudioSessionCategory枚舉
enum {
      kAudioSessionCategory_AmbientSound               = 'ambi',
      kAudioSessionCategory_SoloAmbientSound           = 'solo',
      kAudioSessionCategory_MediaPlayback              = 'medi',
      kAudioSessionCategory_RecordAudio                = 'reca',
      kAudioSessionCategory_PlayAndRecord              = 'plar',
      kAudioSessionCategory_AudioProcessing            = 'proc'
  };
//AudioSession的AudioSessionCategory字符串
/*  Use this category for background sounds such as rain, car engine noise, etc.  
 Mixes with other music. */
AVF_EXPORT NSString *const AVAudioSessionCategoryAmbient;
  
/*  Use this category for background sounds.  Other music will stop playing. */
AVF_EXPORT NSString *const AVAudioSessionCategorySoloAmbient;

/* Use this category for music tracks.*/
AVF_EXPORT NSString *const AVAudioSessionCategoryPlayback;

/*  Use this category when recording audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryRecord;

/*  Use this category when recording and playing back audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryPlayAndRecord;

/*  Use this category when using a hardware codec or signal processor while
 not playing or recording audio. */
AVF_EXPORT NSString *const AVAudioSessionCategoryAudioProcessing;

啟用

有了Category就可以啟動(dòng)AudioSession了掠抬,啟動(dòng)方法:

//AudioSession的啟動(dòng)方法
extern OSStatus AudioSessionSetActive(Boolean active);
extern OSStatus AudioSessionSetActiveWithFlags(Boolean active, UInt32 inFlags);

//AVAudioSession的啟動(dòng)方法
- (BOOL)setActive:(BOOL)active error:(NSError **)outError;
- (BOOL)setActive:(BOOL)active withFlags:(NSInteger)flags error:(NSError **)outError NS_DEPRECATED_IOS(4_0, 6_0);
- (BOOL)setActive:(BOOL)active withOptions:(AVAudioSessionSetActiveOptions)options error:(NSError **)outError NS_AVAILABLE_IOS(6_0);

啟動(dòng)方法調(diào)用后必須要判斷是否啟動(dòng)成功吼野,啟動(dòng)不成功的情況經(jīng)常存在,例如一個(gè)前臺(tái)的app正在播放两波,你的app正在后臺(tái)想要啟動(dòng)AudioSession那就會(huì)返回失敗瞳步。

一般情況下我們?cè)趩?dòng)和停止AudioSession調(diào)用第一個(gè)方法就可以了。但如果你正在做一個(gè)即時(shí)語音通訊app的話(類似于微信腰奋、易信)就需要注意在deactive AudioSession的時(shí)候需要使用第二個(gè)方法单起,inFlags參數(shù)傳入kAudioSessionSetActiveFlag_NotifyOthersOnDeactivationAVAudioSession給options參數(shù)傳入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation)。當(dāng)你的app deactive自己的AudioSession時(shí)系統(tǒng)會(huì)通知上一個(gè)被打斷播放app打斷結(jié)束(就是上面說到的打斷回調(diào))劣坊,如果你的app在deactive時(shí)傳入了NotifyOthersOnDeactivation參數(shù)嘀倒,那么其他app在接到打斷結(jié)束回調(diào)時(shí)會(huì)多得到一個(gè)參數(shù)kAudioSessionInterruptionType_ShouldResume否則就是ShouldNotResume(AVAudioSessionInterruptionOptionShouldResume),根據(jù)參數(shù)的值可以決定是否繼續(xù)播放。

大概流程是這樣的:

  1. 一個(gè)音樂軟件A正在播放测蘑;
  2. 用戶打開你的軟件播放對(duì)話語音灌危,AudioSession active;
  3. 音樂軟件A音樂被打斷并收到InterruptBegin事件碳胳;
  4. 對(duì)話語音播放結(jié)束勇蝙,AudioSession deactive并且傳入NotifyOthersOnDeactivation參數(shù);
  5. 音樂軟件A收到InterruptEnd事件固逗,查看Resume參數(shù)浅蚪,如果是ShouldResume控制音頻繼續(xù)播放,如果是ShouldNotResume就維持打斷狀態(tài)烫罩;

官方文檔中有一張很形象的圖來闡述這個(gè)現(xiàn)象:

image.png

然而現(xiàn)在某些語音通訊軟件和某些音樂軟件卻無視了NotifyOthersOnDeactivation和ShouldResume的正確用法,導(dǎo)致我們經(jīng)常接到這樣的用戶反饋:

你們的app在使用xx語音軟件聽了一段話后就不會(huì)繼續(xù)播放了洽故,但xx音樂軟件可以繼續(xù)播放啊贝攒。

好吧,上面只是吐槽一下时甚。請(qǐng)無視我吧隘弊。

補(bǔ)充

發(fā)現(xiàn)即使之前已經(jīng)調(diào)用過AudioSessionInitialize方法,在某些情況下被打斷之后可能出現(xiàn)AudioSession失效的情況荒适,需要再次調(diào)用AudioSessionInitialize方法來重新生成AudioSession梨熙。否則調(diào)用AudioSessionSetActive會(huì)返回560557673(其他AudioSession方法也雷同,所有方法調(diào)用前必須首先初始化AudioSession)刀诬,轉(zhuǎn)換成string后為”!ini”即kAudioSessionNotInitialized咽扇,這個(gè)情況在iOS 5.1.x上比較容易發(fā)生,iOS 6.x 和 7.x也偶有發(fā)生(具體的原因還不知曉好像和打斷時(shí)直接調(diào)用AudioOutputUnitStop有關(guān)陕壹,又是個(gè)坑爸视)。

所以每次在調(diào)用AudioSessionSetActive時(shí)應(yīng)該判斷一下錯(cuò)誤碼糠馆,如果是上述的錯(cuò)誤碼需要重新初始化一下AudioSession嘶伟。

附上OSStatus轉(zhuǎn)成string的方法:

#import <Endian.h>

NSString * OSStatusToString(OSStatus status)
{
    size_t len = sizeof(UInt32);
    long addr = (unsigned long)&status;
    char cstring[5];

    len = (status >> 24) == 0 ? len - 1 : len;
    len = (status >> 16) == 0 ? len - 1 : len;
    len = (status >>  8) == 0 ? len - 1 : len;
    len = (status >>  0) == 0 ? len - 1 : len;

    addr += (4 - len);

    status = EndianU32_NtoB(status);        // strings are big endian

    strncpy(cstring, (char *)addr, len);
    cstring[len] = 0;

    return [NSString stringWithCString:(char *)cstring encoding:NSMacOSRomanStringEncoding];
}

打斷處理

正常啟動(dòng)AudioSession之后就可以播放音頻了,下面要講的是對(duì)于打斷的處理又碌。之前我們說到打斷的回調(diào)在iOS 5下需要統(tǒng)一管理九昧,在收到打斷開始和結(jié)束時(shí)需要發(fā)送自定義的通知。

使用AudioSession時(shí)打斷回調(diào)應(yīng)該首先獲取kAudioSessionProperty_InterruptionType毕匀,然后發(fā)送一個(gè)自定義的通知并帶上對(duì)應(yīng)的參數(shù)铸鹰。

static void MyAudioSessionInterruptionListener(void *inClientData, UInt32 inInterruptionState)
{
    AudioSessionInterruptionType interruptionType = kAudioSessionInterruptionType_ShouldNotResume;
    UInt32 interruptionTypeSize = sizeof(interruptionType);
    AudioSessionGetProperty(kAudioSessionProperty_InterruptionType,
                            &interruptionTypeSize,
                            &interruptionType);

    NSDictionary *userInfo = @{MyAudioInterruptionStateKey:@(inInterruptionState),
                               MyAudioInterruptionTypeKey:@(interruptionType)};

    [[NSNotificationCenter defaultCenter] postNotificationName:MyAudioInterruptionNotification object:nil userInfo:userInfo];
}

收到通知后的處理方法如下(注意ShouldResume參數(shù))

- (void)interruptionNotificationReceived:(NSNotification *)notification
{
    UInt32 interruptionState = [notification.userInfo[MyAudioInterruptionStateKey] unsignedIntValue];
    AudioSessionInterruptionType interruptionType = [notification.userInfo[MyAudioInterruptionTypeKey] unsignedIntValue];
    [self handleAudioSessionInterruptionWithState:interruptionState type:interruptionType];
}

- (void)handleAudioSessionInterruptionWithState:(UInt32)interruptionState type:(AudioSessionInterruptionType)interruptionType
{
    if (interruptionState == kAudioSessionBeginInterruption)
    {
        //控制UI,暫停播放
    }
    else if (interruptionState == kAudioSessionEndInterruption)
    {
        if (interruptionType == kAudioSessionInterruptionType_ShouldResume)
        {
            OSStatus status = AudioSessionSetActive(true);
            if (status == noErr)
            {
                //控制UI期揪,繼續(xù)播放
            }
        }
    }
}

小結(jié)

關(guān)于AudioSession的話題到此結(jié)束(碼字果然很累掉奄。。)。小結(jié)一下:

  • 如果最低版本支持iOS 5姓建,可以使用AudioSession也可以考慮使用AVAudioSession诞仓,需要有一個(gè)類統(tǒng)一管理AudioSession的所有回調(diào),在接到回調(diào)后發(fā)送對(duì)應(yīng)的自定義通知速兔;
    如果最低版本支持iOS 6及以上墅拭,請(qǐng)使用AVAudioSession,不用統(tǒng)一管理涣狗,接AVAudioSession的通知即可谍婉;
    根據(jù)app的應(yīng)用場(chǎng)景合理選擇Category
    在deactive時(shí)需要注意app的應(yīng)用場(chǎng)景來合理的選擇是否使用NotifyOthersOnDeactivation參數(shù)镀钓;
    在處理InterruptEnd事件時(shí)需要注意ShouldResume的值穗熬。

示例代碼

這里有我自己寫的AudioSession的封裝,如果各位需要支持iOS 5的話可以使用一下丁溅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唤蔗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子窟赏,更是在濱河造成了極大的恐慌妓柜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涯穷,死亡現(xiàn)場(chǎng)離奇詭異棍掐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拷况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門作煌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝠嘉,你說我怎么就攤上這事最疆。” “怎么了蚤告?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵努酸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我杜恰,道長(zhǎng)获诈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任心褐,我火速辦了婚禮舔涎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逗爹。我一直安慰自己亡嫌,他們只是感情好嚎于,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挟冠,像睡著了一般于购。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上知染,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天肋僧,我揣著相機(jī)與錄音,去河邊找鬼控淡。 笑死嫌吠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掺炭。 我是一名探鬼主播辫诅,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼涧狮!你這毒婦竟也來了泥栖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤勋篓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后魏割,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體譬嚣,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年钞它,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拜银。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遭垛,死狀恐怖尼桶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锯仪,我是刑警寧澤泵督,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站庶喜,受9級(jí)特大地震影響小腊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜久窟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一秩冈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斥扛,春花似錦入问、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楣黍。三九已至,卻和暖如春麸折,著一層夾襖步出監(jiān)牢的瞬間锡凝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工垢啼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窜锯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓芭析,卻偏偏與公主長(zhǎng)得像锚扎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馁启,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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