Audio Session:系統(tǒng)與應用程序的中介

Overview

Apple通過audio sessions管理app, app與其他app, app與外部音頻硬件間的行為.使用audio session可以向系統(tǒng)傳達你將如何使用音頻.audio session充當著app與系統(tǒng)間的中介.這樣我們無需了解硬件相關(guān)卻可以操控硬件行為.

1.Audio session
  • 配置audio session類別與模式去告訴系統(tǒng)在app中你想怎么使用音頻
  • 激活audio session使配置的類別與模式可以工作
  • 添加通知,響應重要的audio session通知,例如音頻中斷與硬件線路改變
  • 配置音頻采樣率,聲道數(shù)等信息

1.配置Audio Session

1.1. Audio Session管理Audio

audio session是應用程序與系統(tǒng)間的中介,用于配置音頻行為,APP啟動時,會自動獲得一個audio session的單例對象,配置并且激活它以讓音頻按照期望開始工作.

1.2. Categories代表Audio作用

audio session category代表音頻的主要行為.通過設(shè)置類別, 可以指明app是否使用的當前的輸入或輸出音頻設(shè)備,以及當別的app中正在播放音頻進入我們app時他們的音頻是強制停止還是與我們的音頻一起播放等等.

AVFoundation中定義了很多audio session categories, 你可以根據(jù)需要自定義音頻行為,很多類別支持播放,錄制,錄制與播放同時進行,當系統(tǒng)了解了你定義的音頻規(guī)則,它將提供給你合適的路徑去訪問硬件資源.系統(tǒng)也將確保別的app中的音頻以適合你應用的方式運行.

一些categories可以根據(jù)Mode進一步定制,該模式用于專門指定類別的行為,例如當使用視頻錄制模式時,系統(tǒng)可能會選擇一個不同于默認內(nèi)置麥克風的麥克風,系統(tǒng)還可以針對錄制調(diào)整麥克風的信號強度.

1.3. 中斷處理

如果audio意外中斷,系統(tǒng)會將aduio session置為停用狀態(tài),音頻也會因此立即停止.當一個別的app的audio session被激活并且它的類別未設(shè)置與系統(tǒng)類別或你應用程序類別混合時,中斷就會發(fā)生.你的應用程序在收到中斷通知后應該保存當時的狀態(tài),以及更新用戶界面等相關(guān)操作.通過注冊AVAudioSessionInterruptionNotification可以觀察中斷的開始與結(jié)束點.

1.4. 音頻線路改變

當用戶做出連接,斷開音頻輸入,輸出設(shè)備時,(如:插拔耳機)音頻線路發(fā)生變化,通過注冊AVAudioSessionRouteChangeNotification可以在音頻線路發(fā)生變化時做出相應處理.

1.5. Audio Sessions控制設(shè)備配置

App不能直接控制設(shè)備的硬件,但是audio session提供了一些接口去獲取或設(shè)置一些高級的音頻設(shè)置,如采樣率,聲道數(shù)等等.

1.6. Audio Sessions保護用戶隱私

App如果想使用音頻錄制功能必須請求用戶授權(quán),否則無法使用.

2. 激活Audio Session

在設(shè)置了audio session的category, options, mode后,我們可以激活它以啟動音頻.

2.1. 系統(tǒng)如何解決音頻競爭

隨著app的啟動,內(nèi)置的一些服務(短信,音樂,瀏覽器,電話等)也將在后臺運行.前面的這些內(nèi)置服務都可能產(chǎn)生音頻,如有電話打來,有短信提示等等...

2.2. 激活,停用Audio Session

雖然AVFoundation中播放與錄制可以自動激活你的audio session, 但你可以手動激活并且測試是否激活成功.

系統(tǒng)會停用你的audio session當有電話打進來,鬧鐘響了,或是日歷提醒等消息介入.當處理完這些介入的消息后,系統(tǒng)允許我們手動重新激活audio sesseion.

let session = AVAudioSession.sharedInstance()
do {
    // 1) Configure your audio session category, options, and mode
    // 2) Activate your audio session to enable your custom configuration
    try session.setActive(true)
} catch let error as NSError {
    print("Unable to activate audio session:  \(error.localizedDescription)")
}

如果我們使用AVFoundation對象(AVPlayer, AVAudioRecorder等),系統(tǒng)負責在中斷結(jié)束時重新激活audio session.然而,如果你注冊了通知去重新激活audio session,你可以驗證是否激活成功并且更新用戶界面.

  • 確保在后臺運行的VoIP應用程序的音頻會話僅在應用程序處理呼叫時才處于激活狀態(tài)匀借。在后臺颜阐,若未收到呼叫,VoIP應用程序的音頻會話不應該是激活的。
  • 確保使用錄制類別的應用程序的音頻會話僅在錄制時處于激活狀態(tài)吓肋。在錄制開始和停止之前凳怨,請確保您的會話處于未激活狀態(tài),以允許播放其他聲音,例如系統(tǒng)聲音肤舞。
  • 如果應用程序支持后臺音頻播放或錄制紫新,但在應用程序未主動使用音頻(或準備使用音頻)時,在進入后臺時停用其音頻會話李剖。這樣做允許系統(tǒng)釋放音頻資源芒率,以便其他進程可以使用它們。

2.3. 檢查別的Audio是否正在播放

當你的app被激活前,當前設(shè)備可能正在播放別的聲音,如果你的app是一個游戲的app,知道別的聲音來源顯得十分重要,因為許多游戲允許同時播放別的音樂以增強用戶體驗.

在app進入前臺前,我們可以通過applicationDidBecomeActive:代理方法在其中使用secondaryAudioShouldBeSilencedHint屬性來確定音頻是否正在播放.當別的app正在播放的audio session為不可混音配置時,該值為true. app可以使用此屬性消除次要音頻.

func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleSecondaryAudio),
                                           name: .AVAudioSessionSilenceSecondaryAudioHint,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleSecondaryAudio(notification: Notification) {
    // Determine hint type
    guard let userInfo = notification.userInfo,
        let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
        let type = AVAudioSessionSilenceSecondaryAudioHintType(rawValue: typeValue) else {
            return
    }
 
    if type == .begin {
        // Other app audio started playing - mute secondary audio
    } else {
        // Other app audio stopped playing - restart secondary audio
    }
}

3. 響應中斷

在app中斷后可以通過代碼做出響應.音頻中斷將會導致audio session停用,同時應用程序中音頻立即終止.當一個來自其他app的競爭的audio session被激活且這個audio session類別不支持與你的app進行混音時,中斷發(fā)生.注冊通知后我們可以在得知音頻中斷后做出相應處理.

App會因為中斷被暫停,當用戶接到電話時,鬧鐘,或其他系統(tǒng)事件被觸發(fā)時,當中斷結(jié)束后,App會繼續(xù)運行,但是需要我們手動重新激活audio session.

3.1. 中斷的生命周期

下圖簡單展示了當收到facetime后app的audio session與系統(tǒng)的audio session間激活與未激活狀態(tài)變化.


2.interruput_lifecycle

3.2. 中斷處理方法

通過注冊監(jiān)聽中斷的通知可以在中斷來的時候進行處理.處理中斷取決于你當前正在執(zhí)行的操作:播放,錄制,音頻格式轉(zhuǎn)換,讀取音頻數(shù)據(jù)包等等.一般而言,我們應盡量避免中斷并且做到中斷后盡快恢復.

中斷前

  • 保存狀態(tài)與上下文
  • 更新用戶界面

中斷后

  • 恢復狀態(tài)與上下文
  • 更新用戶界面
  • 重新激活audio session.
Audio technology How interruptions work
AVFoundation framework 系統(tǒng)在中斷時會自動暫停錄制與播放,當中斷結(jié)束后重新激活audio session,恢復錄制與播放
Audio Queue Services, I/O audio unit 系統(tǒng)會發(fā)出中斷通知,開發(fā)者可以保存播放與錄制狀態(tài)并且在中斷結(jié)束后重新激活audio session
System Sound Services 使用系統(tǒng)聲音服務在中斷來臨時保持靜音,如果中斷結(jié)束,聲音自動播放.

3.3. 處理Siri

當處理Siri時,與其他中斷不同,我們在中斷期間需要對Siri進行監(jiān)聽,如在中斷期間,用戶要求Siri去暫停開發(fā)者app中的音頻播放,當app收到中斷結(jié)束的通知時,不應該自動恢復播放.同時,用戶界面需要跟Siri要求的保持一致.

3.4. 監(jiān)聽中斷

注冊AVAudioSessionInterruptionNotification通知可以監(jiān)聽中斷.

func registerForNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleInterruption),
                                           name: .AVAudioSessionInterruption,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleInterruption(_ notification: Notification) {
    // Handle interruption
}

func handleInterruption(_ notification: Notification) {
    guard let info = notification.userInfo,
        let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
        let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
            return
    }
    if type == .began {
        // Interruption began, take appropriate actions (save state, update user interface)
    }
    else if type == .ended {
        guard let optionsValue =
            userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
                return
        }
        let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
        if options.contains(.shouldResume) {
            // Interruption Ended - playback should resume
        }
    }
}

注意: 無法確保在開始中斷后一定有一個結(jié)束中斷,所以,如果沒有結(jié)束中斷,我們在app重新播放音頻時需要總是檢查aduio session是否被激活.

3.5. 響應媒體服務器重置操作

media server通過一個共享服務器進程提供了音頻和其他多媒體功能.盡管很少見,但是如果在你的app正在運行時收到一條重置命令,可以通過注冊AVAudioSessionMediaServicesWereResetNotification通知監(jiān)聽media server是否重置.收到通知后需要做如下操作.

  • 銷毀音頻對象并且創(chuàng)建新的音頻對象(如:players,recorders,converters,audio queues)
  • 重置所有audio狀態(tài),包括AVAudioSession全部屬性
  • 在合適時機重新激活AVAudioSession對象.

注冊AVAudioSessionMediaServicesWereLostNotification可以在media server不可用時收到通知.

如果開發(fā)者的應用程序中需要重置功能,如設(shè)置中有重置選項,可以使用這個方法輕松重置.

4. 線路改變

audio hardware route指定的設(shè)備音頻硬件線路發(fā)生改變.當用戶插拔耳機,系統(tǒng)會自動改變硬件的線路.開發(fā)者可以注冊AVAudioSessionRouteChangeNotification通知在線路變化時作出相應調(diào)整.

3.audio_route_change

如上圖,系統(tǒng)在app啟動時會確定一套音頻線路,而后程序運行期間會繼續(xù)監(jiān)聽當前活躍的音頻線路,在錄制期間,用戶可能插拔耳機,系統(tǒng)會發(fā)送一份改變線路的通知告訴開發(fā)者同時音頻停止,開發(fā)者可以通過代碼決定是否重新激活.

播放與錄制稍有不同,播放時如果用戶拔掉耳機,默認暫停音頻,如果插上耳機,默認繼續(xù)播放.

4.1. 監(jiān)聽Audio線路變化

原因

  • 插拔耳機
  • 連接,斷開藍牙耳機
  • 插拔USB音頻設(shè)備
func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleRouteChange),
                                           name: .AVAudioSessionRouteChange,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleRouteChange(_ notification: Notification) {
 
}

userInfo中提供了關(guān)于線路改變的詳細信息.可以查詢改變原因通過字典中的AVAudioSessionRouteChangeReason,如當新的設(shè)備接入時,原因為AVAudioSessionRouteChangeReason,移除時為AVAudioSessionRouteChangeReasonOldDeviceUnavailable

func handleRouteChange(_ notification: Notification) {
    guard let userInfo = notification.userInfo,
        let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
            return
    }
    switch reason {
    case .newDeviceAvailable:
        // Handle new device available.
    case .oldDeviceUnavailable:
        // Handle old device removed.
    default: ()
    }
}

當有音頻硬件插入時,你可以查詢audio session的currentRoute屬性去確定當前音頻輸出的位置.它將返回一個AVAudioSessionRouteDescription對象包含audio session全部的輸入輸出信息.當一個音頻硬件被移除時,我們也可以從該對象中查詢上一個線路.在以上兩種情況中,我們都可以查詢outputs屬性,通過返回的AVAudioSessionPortDescription對象提供了音頻輸出的全部信息.

func handleRouteChange(notification: NSNotification) {
    guard let userInfo = notification.userInfo,
        let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
            return
    }
    switch reason {
    case .newDeviceAvailable:
        let session = AVAudioSession.sharedInstance()
        for output in session.currentRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
            headphonesConnected = true
        }
    case .oldDeviceUnavailable:
        if let previousRoute =
            userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
            for output in previousRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
                headphonesConnected = false
            }
        }
    default: ()
    }
}

5. 配置設(shè)備硬件

使用audio session屬性,可以在運行時優(yōu)化硬件音頻行為.這樣可以讓代碼適配運行設(shè)備的特性.這樣做同樣適用于用戶對音頻硬件作出的更改.

5.1. 配置初始音頻參數(shù)

使用audio session指定音頻設(shè)備的設(shè)置,如采樣率, I/O緩沖區(qū)時間.

Setting Preferred sample rate Preferred I/O buffer duration
High value Example: 48 kHz, + High audio quality, – Large file or buffer size Example: 500 mS, + Less-frequent file access, – Longer latency
Low value Example: 8 kHz, + Small file or buffer size, – Low audio quality Example: 5 mS,+ Low latency, – Frequent file access

Note: 默認音頻輸入輸出緩沖時間(I/O buffer duration)為大多數(shù)應用提供足夠的相應時間,如44.1kHz音頻大概為20ms響應一次,你可以設(shè)置更低的延遲但相應數(shù)據(jù)量每次過來的也會降低,根據(jù)自己的需求進行選擇.

5.2. 設(shè)置

在激活audio session前必須完成設(shè)置內(nèi)容.如果你正在運行audio session, 先停用它,然后改變設(shè)置重新激活.

let session = AVAudioSession.sharedInstance()
 
// Configure category and mode
do {
    try session.setCategory(AVAudioSessionCategoryRecord, mode: AVAudioSessionModeDefault)
} catch let error as NSError {
    print("Unable to set category:  \(error.localizedDescription)")
}
 
// Set preferred sample rate
do {
    try session.setPreferredSampleRate(44_100)
} catch let error as NSError {
    print("Unable to set preferred sample rate:  \(error.localizedDescription)")
}
 
// Set preferred I/O buffer duration
do {
    try session.setPreferredIOBufferDuration(0.005)
} catch let error as NSError {
    print("Unable to set preferred I/O buffer duration:  \(error.localizedDescription)")
}
 
// Activate the audio session
do {
    try session.setActive(true)
} catch let error as NSError {
    print("Unable to activate session. \(error.localizedDescription)")
}
 
// Query the audio session's ioBufferDuration and sampleRate properties
// to determine if the preferred values were set
print("Audio Session ioBufferDuration: \(session.ioBufferDuration), sampleRate: \(session.sampleRate)")

5.3. 選擇,配置麥克風

一個設(shè)備可能有多個麥克風(內(nèi)置,外接),iOS會根據(jù)當前使用的audio session mode自動選擇一個.mode指定了輸入數(shù)字信號處理(DSP)和可能的線路.輸入線路針對每種模式的用例進行了優(yōu)化,設(shè)置mode還可能影響正在使用的音頻線路.

開發(fā)者可以手動選擇麥克風,甚至可以設(shè)置polar pattern如果硬件支持.

在使用任何音頻設(shè)備之前杖爽,請為您的應用設(shè)置音頻會話類別和模式敲董,然后激活音頻會話。

  • 設(shè)置Preferred Input

為了找到當前設(shè)備連接的音頻輸入設(shè)備,可以使用audio session的availableInputs屬性,該屬性返回一個AVAudioSessionPortDescription對象的數(shù)組,描述當前可用輸入設(shè)備端口,端口用portType進行標識.可以使用setPreferredInput:error:設(shè)置可用的音頻輸入設(shè)備.

  • 設(shè)置Preferred Data Source

部分端口如內(nèi)置麥克風,USB等支持數(shù)據(jù)源(data source),應用程序可以通過查詢端口的dataSources屬性發(fā)現(xiàn)可用的數(shù)據(jù)源.對于內(nèi)置麥克風慰安,返回的數(shù)據(jù)源描述對象代表每個單獨的麥克風腋寨。不同的設(shè)備為內(nèi)置麥克風返回不同的值。例如化焕,iPhone 4和iPhone 4S有兩個麥克風:底部和頂部萄窜。 iPhone 5有三個麥克風:底部,前部和后部撒桨。

可以通過數(shù)據(jù)源描述的location屬性(上查刻,下)和orientation屬性(前,后等)的組合來識別各個內(nèi)置麥克風凤类。應用程序可以使用AVAudioSessionPortDescription對象的setPreferredDataSource:error:方法設(shè)置首選數(shù)據(jù)源穗泵。

  • 設(shè)置 Preferred Polar Pattern

某些iOS設(shè)備支持為某些內(nèi)置麥克風配置麥克風極性模式。麥克風的極性模式定義了其對聲音相對于聲源方向的靈敏度谜疤。使用supportedPolarPatterns屬性返回數(shù)據(jù)源是否支持此模式,此屬性返回數(shù)據(jù)源支持的極坐標模式數(shù)組(如心形或全向)佃延,或者在沒有可選模式時返回nil。如果數(shù)據(jù)源具有許多支持的極坐標模式夷磕,則可以使用數(shù)據(jù)源描述的setPreferredPolarPattern:error:方法設(shè)置首選極坐標模式履肃。

  • 選擇特定麥克風并且設(shè)置polar pattern.
// Preferred Mic = Front, Preferred Polar Pattern = Cardioid
let preferredMicOrientation = AVAudioSessionOrientationFront
let preferredPolarPattern = AVAudioSessionPolarPatternCardioid
 
// Retrieve your configured and activated audio session
let session = AVAudioSession.sharedInstance()
 
// Get available inputs
guard let inputs = session.availableInputs else { return }
 
// Find built-in mic
guard let builtInMic = inputs.first(where: {
    $0.portType == AVAudioSessionPortBuiltInMic
}) else { return }
 
// Find the data source at the specified orientation
guard let dataSource = builtInMic.dataSources?.first (where: {
    $0.orientation == preferredMicOrientation
}) else { return }
 
// Set data source's polar pattern
do {
    try dataSource.setPreferredPolarPattern(preferredPolarPattern)
} catch let error as NSError {
    print("Unable to preferred polar pattern: \(error.localizedDescription)")
}
 
// Set the data source as the input's preferred data source
do {
    try builtInMic.setPreferredDataSource(dataSource)
} catch let error as NSError {
    print("Unable to preferred dataSource: \(error.localizedDescription)")
}
 
// Set the built-in mic as the preferred input
// This call will be a no-op if already selected
do {
    try session.setPreferredInput(builtInMic)
} catch let error as NSError {
    print("Unable to preferred input: \(error.localizedDescription)")
}
 
// Print Active Configuration
session.currentRoute.inputs.forEach { portDesc in
    print("Port: \(portDesc.portType)")
    if let ds = portDesc.selectedDataSource {
        print("Name: \(ds.dataSourceName)")
        print("Polar Pattern: \(ds.selectedPolarPattern ?? "[none]")")
    }
}
Running this code on an iPhone 6s produces the following console output:

Port: MicrophoneBuiltIn
Name: Front
Polar Pattern: Cardioid

5.4. 模擬器運行

可以在模擬器或設(shè)備上運行您的應用。但是坐桩,Simulator不會模擬不同進程或音頻線路更改中的音頻會話之間的大多數(shù)交互尺棋。在Simulator中運行應用程序時,您不能:

  • 調(diào)用中斷
  • 模擬插入或拔出耳機
  • 更改靜音開關(guān)的設(shè)置
  • 模擬屏幕鎖定
  • 測試音頻混合行為 - 即播放音頻以及來自其他應用(例如音樂應用)的音頻
#if arch(i386) || arch(x86_64)
    // Execute subset of code that works in the Simulator
#else
    // Execute device-only code as well as the other code
#endif

保護用戶隱私

為了保護用戶隱私绵跷,應用必須在錄制音頻之前詢問并獲得用戶的許可膘螟。如果用戶未授予許可,則僅記錄靜音。當您使用支持錄制的類別并且應用程序嘗試使用輸入線路時,系統(tǒng)會自動提示用戶獲得權(quán)限肮柜。

您可以使用requestRecordPermission:方法手動請求權(quán)限,而不是等待系統(tǒng)提示用戶提供記錄權(quán)限脊阴。使用此方法可以讓您的應用獲得權(quán)限,而不會中斷應用的自然流動,從而獲得更好的用戶體驗嘿期。

AVAudioSession.sharedInstance().requestRecordPermission { granted in
    if granted {
        // User granted access. Present recording interface.
    } else {
        // Present message to user indicating that recording
        // can't be performed until they change their preference
        // under Settings -> Privacy -> Microphone
    }
}

從iOS 10開始品擎,所有訪問任何設(shè)備麥克風的應用都必須靜態(tài)聲明其意圖。為此备徐,應用程序現(xiàn)在必須在其Info.plist文件中包含NSMicrophoneUsageDescription鍵萄传,并為此密鑰提供目的字符串。當系統(tǒng)提示用戶允許訪問時蜜猾,此字符串將顯示為警報的一部分秀菱。如果應用程序嘗試訪問任何設(shè)備的麥克風而沒有此鍵和值,則應用程序?qū)⒔K止蹭睡。


Apple 官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衍菱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肩豁,更是在濱河造成了極大的恐慌脊串,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件清钥,死亡現(xiàn)場離奇詭異琼锋,居然都是意外死亡,警方通過查閱死者的電腦和手機祟昭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門缕坎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篡悟,你說我怎么就攤上這事念赶。” “怎么了恰力?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旗吁。 經(jīng)常有香客問我踩萎,道長,這世上最難降的妖魔是什么很钓? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任香府,我火速辦了婚禮,結(jié)果婚禮上码倦,老公的妹妹穿的比我還像新娘企孩。我一直安慰自己,他們只是感情好袁稽,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布勿璃。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪补疑。 梳的紋絲不亂的頭發(fā)上歧沪,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音莲组,去河邊找鬼诊胞。 笑死,一個胖子當著我的面吹牛锹杈,可吹牛的內(nèi)容都是我干的撵孤。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼竭望,長吁一口氣:“原來是場噩夢啊……” “哼邪码!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起市框,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤霞扬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枫振,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喻圃,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年粪滤,在試婚紗的時候發(fā)現(xiàn)自己被綠了斧拍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡杖小,死狀恐怖肆汹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情予权,我是刑警寧澤昂勉,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站扫腺,受9級特大地震影響岗照,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笆环,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一攒至、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躁劣,春花似錦迫吐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熙宇。三九已至,卻和暖如春梧却,著一層夾襖步出監(jiān)牢的瞬間奇颠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工放航, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烈拒,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓广鳍,卻偏偏與公主長得像荆几,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赊时,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361