iOS中聲音播放的各種方法總結

前言

這兩天禁(晉)煙(嫣)的秀恩愛,身為程序員的我們又被默默的送了一把狗糧,這段時間一直在忙公司項目,兩個多月都沒有寫過文章了,今天閑來無事想把iOS中播放音樂(包括段音效)的部分拿出來總結一下。

主要部分:

1.音效的播放
2.音樂的播放(本地, 網絡)
3.音頻隊列服務

1.音效播放(AudioToolbox/AudioToolbox.h)

音頻文件必須打包成.caf、.aif、.wav中的一種(注意這是官方文檔的說法,實際測試發(fā)現(xiàn)一些.mp3也可以播放)
這個段音效播放不能大于30s,這個30s不是我說的,是蘋果的API說的


AudioServices_h.png
創(chuàng)建音效的ID,音效的播放和銷毀都靠這個ID來執(zhí)行

AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)

播放音效

AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)

iOS9以后可以用的,帶有block回調的播放

AudioServicesPlaySystemSoundWithCompletion(SystemSoundID inSystemSoundID, void (^__nullable inCompletionBlock)(void))

帶有震動的播放

AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)

iOS9以后可以用的,帶有block回調的播放

AudioServicesPlayAlertSoundWithCompletion( SystemSoundID inSystemSoundID,void (^__nullable inCompletionBlock)(void))

在iOS9之前,如何判斷一個音效是否播放完成呢?(利用下面的方法)

AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID,CFRunLoopRef __nullable inRunLoop, CFStringRef __nullable inRunLoopMode,AudioServicesSystemSoundCompletionProc inCompletionRoutine,void * __nullable inClientData)

銷毀音效的播放

AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)

下面對上面的方法的演示,播放一些音效, 播放48s的mp3時會報錯

static SystemSoundID soundID = 0;

- (IBAction)play:(id)sender {
    
//    NSString *str = [[NSBundle mainBundle] pathForResource:@"vcyber_waiting" ofType:@"wav"];
    NSString *str = [[NSBundle mainBundle] pathForResource:@"28s" ofType:@"mp3"];
//    NSString *str = [[NSBundle mainBundle] pathForResource:@"48s" ofType:@"mp3"];
    NSURL *url = [NSURL fileURLWithPath:str];
    
    
    AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
//
//    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallBack, NULL);
//    
//    //AudioServicesPlaySystemSound(soundID);
//    
//    AudioServicesPlayAlertSound(soundID);

    
//    AudioServicesPlaySystemSoundWithCompletion(soundID, ^{
//        NSLog(@"播放完成");
//        AudioServicesDisposeSystemSoundID(soundID);
//    });
    
    AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
        NSLog(@"播放完成");
    });
    
}

void soundCompleteCallBack(SystemSoundID soundID, void * clientDate) {
    NSLog(@"播放完成");
    AudioServicesDisposeSystemSoundID(soundID);
}

- (IBAction)stop:(id)sender {
    AudioServicesDisposeSystemSoundID(soundID);
}

2.本地音樂播放

AVAudioPlayer

AVAudioPlayer是播放本地音樂最常到的,這個類對于大多數(shù)人來說應該很常用,這里不多說,說一下它的基本用法和代理的用法,直接上代碼,代碼注釋很詳細

@interface LocalMusicViewController ()<AVAudioPlayerDelegate>

/**
 播放器
 */
@property (nonatomic, strong) AVAudioPlayer *player;

/**
 播放進度條
 */
@property (weak, nonatomic) IBOutlet UIProgressView *progress;

/**
 改變播放進度滑塊
 */
@property (weak, nonatomic) IBOutlet UISlider *progressSlide;

/**
 改變聲音滑塊
 */
@property (weak, nonatomic) IBOutlet UISlider *volum;

/**
 改變進度條滑塊顯示的定時器
 */
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation LocalMusicViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSError *err;
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"1" withExtension:@"mp3"];
//    初始化播放器
    _player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&err];
    self.volum.value = 0.5;
//    設置播放器聲音
    _player.volume = self.volum.value;
//    設置代理
    _player.delegate = self;
//    設置播放速率
    _player.rate = 1.0;
//    設置播放次數(shù) 負數(shù)代表無限循環(huán)
    _player.numberOfLoops = -1;
//    準備播放
    [_player prepareToPlay];
    self.progress.progress = 0;
    self.progressSlide.value = 0;
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(change) userInfo:nil repeats:YES];
    
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
}

- (void)change {
    self.progress.progress = _player.currentTime / _player.duration;
}

- (IBAction)progressChange:(UISlider *)sender {
//    改變當前的播放進度
    _player.currentTime = sender.value * _player.duration;
    self.progress.progress = sender.value;
    
}
- (IBAction)volumChange:(UISlider *)sender {
//    改變聲音大小
    _player.volume = sender.value;
}

- (IBAction)player:(id)sender {
//    開始播放
    [_player play];
}

- (IBAction)stop:(id)sender {
//    暫停播放
    [_player stop];
}

#pragma mark --AVAudioPlayerDelegate
/**
 完成播放, 但是在打斷播放和暫停、停止不會調用
 */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
    
}

/**
 播放過程中解碼錯誤時會調用
 */
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {
    
}

/**
 播放過程被打斷

 */
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 8_0) {
    
}

/**
 打斷結束
*/
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0) {
    
}

/**
 打斷結束

 */
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withFlags:(NSUInteger)flags NS_DEPRECATED_IOS(4_0, 6_0) {
    
}

/**
 這個方法被上面的方法代替了
 */
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 6_0) {
    
}

網絡音樂播放(AVPlayer)

AVPlayer是播放網絡音樂和網絡視頻最常用到的,它可以自己緩存網絡數(shù)據(jù),然后播放,AVPlayer在播放視頻時必須創(chuàng)建一個AVPlayerLayer用來展示視頻,如果播放音樂,聲音就不用創(chuàng)建這個對象。這里簡單演示一下網絡播放音樂

1. 通過網絡鏈接創(chuàng)建AVPlayerItem

AVPlayerItem的初始化方法很多,我這里直接用initWithURL:這個方法創(chuàng)建

- (AVPlayerItem *)getItemWithIndex:(NSInteger)index {
    NSURL *url = [NSURL URLWithString:self.musicArray[index]];
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
    //KVO監(jiān)聽播放狀態(tài)
    [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //KVO監(jiān)聽緩存大小
    [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    //通知監(jiān)聽item播放完畢
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playOver:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
    return item;
}
2.實現(xiàn)KVO的方法,根據(jù)keyPath來判斷觀察的屬性是哪一個
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    AVPlayerItem *item = object;
    
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
                NSLog(@"未知狀態(tài),不能播放");
                break;
            case AVPlayerStatusReadyToPlay:
                NSLog(@"準備完畢,可以播放");
                break;
            case AVPlayerStatusFailed:
                NSLog(@"加載失敗, 網絡相關問題");
                break;
                
            default:
                break;
        }
    }
    
    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        NSArray *array = item.loadedTimeRanges;
        //本次緩存的時間
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];
        NSTimeInterval totalBufferTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //緩存的總長度
        self.bufferProgress.progress = totalBufferTime / CMTimeGetSeconds(item.duration);
    }
}
3.懶加載AVPlayer
- (AVPlayer *)player {
    if (!_player) {
//        根據(jù)鏈接數(shù)組獲取第一個播放的item, 用這個item來初始化AVPlayer
        AVPlayerItem *item = [self getItemWithIndex:self.currentIndex];
//        初始化AVPlayer
        _player = [[AVPlayer alloc] initWithPlayerItem:item];
        __weak typeof(self)weakSelf = self;
//        監(jiān)聽播放的進度的方法,addPeriodicTime: ObserverForInterval: usingBlock:
        /*
         DMTime 每到一定的時間會回調一次,包括開始和結束播放
         block回調,用來獲取當前播放時長
         return 返回一個觀察對象,當播放完畢時需要,移除這個觀察
         */
        _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
            float current = CMTimeGetSeconds(time);
            if (current) {
                [weakSelf.progressView setProgress:current / CMTimeGetSeconds(item.duration) animated:YES];
                weakSelf.progressSlide.value = current / CMTimeGetSeconds(item.duration);
            }
        }];
    }
    return _player;
}
4.播放和暫停
//  播放
- (IBAction)play:(id)sender {
    [self.player play];
}

//暫停
- (IBAction)pause:(id)sender {
    [self.player pause];
}
5.下一首和上一首
- (IBAction)next:(UIButton *)sender {
    [self removeObserver];
   self.currentIndex ++;
    if (self.currentIndex >= self.musicArray.count) {
        self.currentIndex = 0;
    }
//  這個方法是用一個item取代當前的item
    [self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];
    [self.player play];
}

- (IBAction)last:(UIButton *)sender {
    [self removeObserver];
    self.currentIndex --;
    if (self.currentIndex < 0) {
        self.currentIndex = 0;
    }
//  這個方法是用一個item取代當前的item
    [self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];
    [self.player play];
}

// 在播放另一個時,要移除當前item的觀察者,還要移除item播放完成的通知
- (void)removeObserver {
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
    [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
6.控制播放進度,這個也有很多的方法,如果不是太精確,用- (void)seekToTime:(CMTime)time:這個方法就行,如果要精確的用這個- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter
- (IBAction)changeProgress:(UISlider *)sender {
    if (self.player.status == AVPlayerStatusReadyToPlay) {
        [self.player seekToTime:CMTimeMake(CMTimeGetSeconds(self.player.currentItem.duration) * sender.value, 1)];
    }
}

音頻隊列服務(Audio Queue Services)

在AudioToolbox框架中的音頻隊列服務,是用來播放網絡流媒體的一個框架,它完全可以做到音頻播放和錄制,一個音頻服務隊列有三個部分組成:
1.三個緩沖器Buffers:沒個緩沖器都是一個存儲音頻數(shù)據(jù)的臨時倉庫。
2.一個緩沖隊列Buffer Queue:一個包含音頻緩沖器的有序隊列。
3.一個回調CallBack:一個自定義的隊列回調函數(shù)。
在音頻播放緩沖隊列中,將音頻讀取到緩沖器中,一旦一個緩沖器填充滿之后就放到緩沖隊列中,然后繼續(xù)填充其他緩沖器;當開始播放時,則從第一個緩沖器中讀取音頻進行播放;一旦播放完之后就會觸發(fā)回調函數(shù),開始播放下一個緩沖器中的音頻,同時填充第一個緩沖器放;填充滿之后再次放回到緩沖隊列。下面是官方詳細的流程:

Playback_Audio_Queues.png

AudioQueue的工作大致流程:
1.創(chuàng)建AudioQueue,創(chuàng)建BufferArray數(shù)組,用于存放AudioQueueBufferRef
2.通過AudioQueueAllocateBuffer創(chuàng)建AudioQueueBufferRef一般2-3個,放入到BufferArray數(shù)組中
3.有數(shù)據(jù)時從buffer數(shù)組取出一個buffermemcpy數(shù)據(jù)后用AudioQueueEnqueueBuffer方法把buffer插入AudioQueue
4.AudioQueue中存在Buffer后,調用AudioQueueStart播放。(具體等到填入多少buffer后再播放可以自己控制,只要能保證播放不間斷即可)
5.AudioQueue播放音樂后消耗了某個buffer,在另一個線程回調并送出該buffer,把buffer放回BufferArray供下一次使用
6.返回步驟3繼續(xù)循環(huán)直到播放結束

常用API
創(chuàng)建AudioQueue
第一個參數(shù)表示需要播放的音頻數(shù)據(jù)格式類型,是一個AudioStreamBasicDescription對象,是使用AudioFileStream或者AudioFile解析出來的數(shù)據(jù)格式信息;
第二個參數(shù)AudioQueueOutputCallback是某塊Buffer被使用之后的回調;
第三個參數(shù)為上下文對象;
第四個參數(shù)inCallbackRunLoop為AudioQueueOutputCallback需要在的哪個RunLoop上被回調,如果傳入NULL的話就會再AudioQueue的內部RunLoop中被回調,所以一般傳NULL就可以了;
第五個參數(shù)inCallbackRunLoopMode為RunLoop模式,如果傳入NULL就相當于kCFRunLoopCommonModes,也傳NULL就可以了;
第六個參數(shù)inFlags是保留字段,目前沒作用,傳0;
第七個參數(shù),返回生成的AudioQueue實例;
返回值用來判斷是否成功創(chuàng)建(OSStatus == noErr)。
extern OSStatus             
AudioQueueNewOutput( const AudioStreamBasicDescription *inFormat,
                      AudioQueueOutputCallback        inCallbackProc,
                     void * __nullable               inUserData,
                       CFRunLoopRef __nullable         inCallbackRunLoop,
                       CFStringRef __nullable          inCallbackRunLoopMode,
                       UInt32                          inFlags,
                      AudioQueueRef __nullable * __nonnull outAQ)          

參數(shù)和上面基本相同,只是把RunLoop換成了dispatch queue
AudioQueueNewOutputWithDispatchQueue(AudioQueueRef __nullable * __nonnull outAQ,
                                    const AudioStreamBasicDescription *inFormat,
                                    UInt32                          inFlags,
                                    dispatch_queue_t                inCallbackDispatchQueue,
                                    AudioQueueOutputCallbackBlock   inCallbackBlock)        
創(chuàng)建Buffer
第一個參數(shù)方法傳入AudioQueue實例
第二個參數(shù)Buffer大小
第三個傳出的BufferArray實例;
extern OSStatus
AudioQueueAllocateBuffer(AudioQueueRef    inAQ,
                          UInt32    inBufferByteSize,
                          AudioQueueBufferRef __nullable * __nonnull outBuffer)  

比上面的方法多了一個inNumberPacketDescriptions,這個參數(shù)可以指定生成的Buffer中PacketDescriptions的個數(shù)
extern OSStatus
AudioQueueAllocateBufferWithPacketDescriptions(
                                    AudioQueueRef           inAQ,
                                    UInt32                  inBufferByteSize,
                                    UInt32                  inNumberPacketDescriptions,
                                    AudioQueueBufferRef __nullable * __nonnull outBuffer)  
釋放buffer
第一個參數(shù)AudioQueue實例
第二個參數(shù)指定的buffer
extern OSStatus
AudioQueueFreeBuffer(               AudioQueueRef           inAQ,
                                    AudioQueueBufferRef     inBuffer) 
插入buffer
第一個參數(shù)AudioQueue實例
第二個參數(shù)指定的Buffer
第三個參數(shù)數(shù)據(jù)包的個數(shù)
第四個參數(shù)數(shù)據(jù)包描述
extern OSStatus
AudioQueueEnqueueBuffer(            AudioQueueRef                       inAQ,
                                    AudioQueueBufferRef                 inBuffer,
                                    UInt32                              inNumPacketDescs,
                                    const AudioStreamPacketDescription * __nullable inPacketDescs)

上面的方法基本滿足要求,這個方法對插入的buffer進行額外的更多的操作
extern OSStatus
AudioQueueEnqueueBufferWithParameters(
                                    AudioQueueRef                                inAQ,
                                    AudioQueueBufferRef                          inBuffer,
                                    UInt32                                       inNumPacketDescs,
                                    const AudioStreamPacketDescription * __nullable inPacketDescs,
                                    UInt32                                       inTrimFramesAtStart,
                                    UInt32                                       inTrimFramesAtEnd,
                                    UInt32                                       inNumParamValues,
                                    const AudioQueueParameterEvent * __nullable  inParamValues,
                                    const AudioTimeStamp * __nullable            inStartTime,
                                    AudioTimeStamp * __nullable                  outActualStartTime)     __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
開始播放
第一個參數(shù)AudioQueue實例
第二個參數(shù)播放時間,如果直接開始播放 傳NULL
extern OSStatus
AudioQueueStart(                    AudioQueueRef                     inAQ,
                                    const AudioTimeStamp * __nullable inStartTime)  
解碼數(shù)據(jù),不常用,調用開始播放會自動解碼
extern OSStatus
AudioQueuePrime(                    AudioQueueRef           inAQ,
                                    UInt32                  inNumberOfFramesToPrepare,
                                    UInt32 * __nullable     outNumberOfFramesPrepared) 
停止播放
第二個參數(shù)Bool值,控制是否立即停止,如果傳false,會把Enqueue的所有buffer播放完成再停止
extern OSStatus
AudioQueueStop(                     AudioQueueRef           inAQ,
                                    Boolean                 inImmediate)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
暫停播放
extern OSStatus
AudioQueuePause(                    AudioQueueRef           inAQ)       __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
重置解碼器
這個方法會播放完隊列中的buffer后重置解碼器,防止當前的解碼器影響下一段音頻,比如切換歌曲的時候,如果和AudioQueueStop(AQ,false)
一起使用并不會起效,因為Stop方法的false參數(shù)也會做同樣的事情。
extern OSStatus
AudioQueueFlush(                    AudioQueueRef           inAQ)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
重置AudioQueue
重置AudioQueue會清除所有已經Enqueue的buffer,并觸發(fā)AudioQueueOutputCallback,調用AudioQueueStop方法時同樣會觸發(fā)該方法。這個方法的直接調用一般在seek時使用,用來清除殘留的buffer(seek時還有一種做法是先AudioQueueStop
,等seek完成后重新start)。
extern OSStatus
AudioQueueReset(                    AudioQueueRef           inAQ)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
獲取播放時間
調用時傳入AudioTimeStamp,從這個結構體當中獲取播放時間
extern OSStatus
AudioQueueGetCurrentTime(           AudioQueueRef                    inAQ,
                                    AudioQueueTimelineRef __nullable inTimeline,
                                    AudioTimeStamp * __nullable      outTimeStamp,
                                    Boolean * __nullable             outTimelineDiscontinuity)       __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
銷毀AudioQueue
參數(shù)的意義基本和AudioQueueStop一樣
extern OSStatus
AudioQueueDispose(                  AudioQueueRef           inAQ, 
                                    Boolean                 inImmediate)            __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
AudioQueue參數(shù)
AudioQueueGetParameter
AudioQueueSetParameter
參數(shù)列表
CF_ENUM(AudioQueueParameterID)
{
    kAudioQueueParam_Volume         = 1,
    kAudioQueueParam_PlayRate       = 2,
    kAudioQueueParam_Pitch          = 3,
    kAudioQueueParam_VolumeRampTime = 4,
    kAudioQueueParam_Pan            = 13
};
AudioQueue屬性
AudioQueueGetPropertySize
AudioQueueGetProperty
AudioQueueSetProperty
屬性列表
CF_ENUM(AudioQueuePropertyID) {
    kAudioQueueProperty_IsRunning               = 'aqrn',       // value is UInt32

    kAudioQueueDeviceProperty_SampleRate        = 'aqsr',       // value is Float64
    kAudioQueueDeviceProperty_NumberChannels    = 'aqdc',       // value is UInt32
    kAudioQueueProperty_CurrentDevice           = 'aqcd',       // value is CFStringRef
    
    kAudioQueueProperty_MagicCookie             = 'aqmc',       // value is void*
    kAudioQueueProperty_MaximumOutputPacketSize = 'xops',       // value is UInt32
    kAudioQueueProperty_StreamDescription       = 'aqft',       // value is AudioStreamBasicDescription
       
    kAudioQueueProperty_ChannelLayout           = 'aqcl',       // value is AudioChannelLayout
    kAudioQueueProperty_EnableLevelMetering     = 'aqme',       // value is UInt32
    kAudioQueueProperty_CurrentLevelMeter       = 'aqmv',       // value is array of AudioQueueLevelMeterState, 1 per channel
    kAudioQueueProperty_CurrentLevelMeterDB     = 'aqmd',       // value is array of AudioQueueLevelMeterState, 1 per channel

    kAudioQueueProperty_DecodeBufferSizeFrames  = 'dcbf',       // value is UInt32
    kAudioQueueProperty_ConverterError          = 'qcve',       // value is UInt32

    kAudioQueueProperty_EnableTimePitch         = 'q_tp',       // value is UInt32, 0/1
    kAudioQueueProperty_TimePitchAlgorithm      = 'qtpa',       // value is UInt32. See values below.
    kAudioQueueProperty_TimePitchBypass         = 'qtpb',       // value is UInt32, 1=bypassed
};
監(jiān)聽屬相變化相關方法
AudioQueueAddPropertyListener
AudioQueueRemovePropertyListener

總結:

這里說的東西都比(能)較(力)基(有)礎(限),其實AudioQueue的功能還有很多,如果大家想去研究比較細致的AudioQueue的使用,這里給大家推薦兩個github地址,一個是AudioStreamer,一個是FreeStreamer,這里的兩個播放都是使用AudioQueue實現(xiàn)的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容