AVFoundation框架(三) - 媒體資源和元數(shù)據(jù)

通過使用資源對象,并通過構(gòu)建它的元數(shù)據(jù)編輯來了解AVFoundation的特性.

1. 媒體資源AVAsset

AVFoundation 中最重要的就是AVAsset這個(gè)抽象類,它定義了媒體資源混合呈現(xiàn)的方式,將媒體資源的靜態(tài)屬性模塊化一個(gè)整體.比如標(biāo)題,時(shí)長,元數(shù)據(jù)等.
我們使用AVAssetTrack可以從AVAsset資源容器中拿到軌道信息和上面的內(nèi)容。

AVAsset主要是抽象化了基本媒體資源的格式 , 跟資源文件一對一映射, 因此我們就不需考慮不種格式,而使用單一統(tǒng)一的方式處理.

  • 創(chuàng)建資源
例: 從照片庫中視頻文件創(chuàng)建一個(gè)AVAsset資源
  PHPhotoLibrary *libary = [PHPhotoLibrary sharedPhotoLibrary];
  [libary performChanges:^{
    // 獲取視頻類資源.可以用類似 NSArray 的接口來訪問結(jié)果內(nèi)的集合。它會按需動(dòng)態(tài)加載內(nèi)容并且緩存最近請求的內(nèi)容
    PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:nil];

    // 獲取第一個(gè)視頻.
    [result enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndex:0] options:0 usingBlock:^(PHAsset   * _Nonnull  phAsset, NSUInteger idx, BOOL * _Nonnull stop) {
      
      if (phAsset) {
        __weak __typeof(self) weakSelf = self;
        // PHAsset轉(zhuǎn)換 AVAsset 
        [[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:nil resultHandler:^(AVAsset * avAsset, AVAudioMix * audioMix, NSDictionary * info) {
         // dispatch_async(dispatch_get_main_queue(), ^{ // 按需要添加
            [weakSelf doSomethingWithAVAsset:avAsset];
          //});
        }];
      }
    }];
  } completionHandler:^(BOOL success, NSError * _Nullable error) {
    if (!success) {
      NSLog(@"PHPhotoLibrary error:%@",error);
    }
  }];

AVAsset資源通常使用assetWithURL:方法來獲取,但是iOS使用新的了Photos照片庫,沒有了url的定義,所以這里用PHImageManager來進(jìn)行轉(zhuǎn)換.

  • 訪問資源屬性:AVAsset使用一種高效的設(shè)計(jì)模式,即延遲加載資源的屬性,只有當(dāng)使用時(shí)候才加載.這樣可以快速創(chuàng)建資源 . 但有時(shí)資源屬性的訪問是同步發(fā)生的,而正在請求的屬性沒有預(yù)先載入時(shí),就會造成程序阻塞. 所以**要使用異步方式查詢資源的屬性. **
    AVAsset和AVAssetTrack通過AVAsynchronousKeyValueLoading協(xié)議實(shí)現(xiàn)異步查詢屬性功能.
- (void)doSomethingWithAVAsset:(AVAsset*) asset {
  NSArray *keys = @[@"tracks"];
  // 異步加載資源的tracks屬性
  [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
    // 先捕獲 tracks 屬性狀態(tài),再據(jù)此做處理
    NSError *error = nil;
    AVKeyValueStatus status = [asset statusOfValueForKey:@"tracks" error:&error];
    switch (status) {
      case AVKeyValueStatusLoaded:
        break;
      case AVKeyValueStatusFailed:
        break;
      default:
        ;
    }
  }];
}

如果想訪問資源的多個(gè)屬性時(shí), 雖然loadValuesAsynchronouslyForKeys:只會調(diào)用一次,但是每個(gè)屬性的狀態(tài)不一定一致,所以要分開判斷.

1.1 元數(shù)據(jù)

每個(gè)媒體資源類型都具有唯一的格式,因此開發(fā)者通常需要對相應(yīng)格式的底層讀寫操作有所了解. 而AVFoundation 框架中的媒體格式都嵌入到了描述其內(nèi)容的元數(shù)據(jù)中. 所以我們可以使用一套統(tǒng)一的方法來直接處理元數(shù)據(jù) .

**元數(shù)據(jù)格式: ** Apple環(huán)境下媒體類型主要是: QuickTime(mov)都办、MPEG-4 video(mp4,mpv) 和MPEG-4 audio(m4a)、MPEG-Layer III audio (mp3);

  1. QuickTime
    QuickTime定義了.mov文件的內(nèi)部結(jié)構(gòu). 通常一部QuickTime電影會包含兩種類型元數(shù)據(jù):標(biāo)準(zhǔn)元數(shù)據(jù)(/moov/meta/ilst/中)和用戶元數(shù)據(jù)(/moov/udta/中).顧名思義: 用戶元數(shù)據(jù)就是包括演唱者,版權(quán)信息以及任何對應(yīng)用程序有幫助的額外信息.
  2. MPEG-4 音頻和視頻
    MP4文件直接派生于QuickTime文件格式,所以很多解析QuickTime文件的工具也能解析MPEG-4.它的元數(shù)據(jù)保存在/moov/udta/meta/ilst中

該類型文件還有一些變化的擴(kuò)展名,如.m4v,.m4a,.m4p和.m4b. 它們都使用MPEG-4容器格式,但包含了一些附加的擴(kuò)展功能;m4v是帶有蘋果公司針對FairPlay加密和AC3-audio擴(kuò)展的格式.m4a專門針對音頻,告訴使用者此文件只含音頻資源.m4b用于有聲讀物,通常包含章節(jié)標(biāo)簽痊末、書簽等功能.

  1. MP3
    MP3文件與上面兩種有顯著區(qū)別,MP3不適用容器格式,而使用編碼音頻數(shù)據(jù),使用ID3v2格式來保存音頻的描述信息,包含演唱者,唱片公司等信息.AVFoundation只支持MP3的解碼讀取.
1.2 獲取元數(shù)據(jù)

AVAsset和AVAssetTrack都可以實(shí)現(xiàn)查詢相關(guān)元數(shù)據(jù)的功能,通過AVMetadataItem類的接口來訪問元數(shù)據(jù).大部分情況我們使用AVAsset,除非你要獲取低層級元數(shù)據(jù)的信息. 另外AVFoundation使用鍵空間(keys space)將相關(guān)鍵組合在一起.以方便實(shí)現(xiàn)對AVMetadataItem實(shí)例集合分類篩選.每個(gè)資源至少包含兩個(gè)鍵空間: commonMetadataavailableMetadataFormats. 前者用來定義所有支持的媒體類型的鍵,包括:曲名,歌手,插圖等常見元素. 后者用來包含用來定義元數(shù)據(jù)格式的NSString對象和相關(guān)元數(shù)據(jù)信息的NSArray.

#define COMMON_META_KEY     @"commonMetadata"
#define AVAILABLE_META_KEY  @"availableMetadataFormats"
// 兩種鍵空間
    NSArray *keys = @[COMMON_META_KEY, AVAILABLE_META_KEY];
// ios支持的元數(shù)據(jù)格式
    NSArray *acceptedFormats = @[                                                
            AVMetadataFormatQuickTimeMetadata,
            AVMetadataFormatiTunesMetadata,
            AVMetadataFormatID3Metadata
        ];

    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{ 
  // 1. 判斷資源屬性加載狀態(tài).
        AVKeyValueStatus commonStatus =
            [self.asset statusOfValueForKey:COMMON_META_KEY error:nil];
        AVKeyValueStatus formatsStatus =
            [self.asset statusOfValueForKey:AVAILABLE_META_KEY error:nil];
        self.prepared = (commonStatus == AVKeyValueStatusLoaded) &&         
                        (formatsStatus == AVKeyValueStatusLoaded);
  // 2. 獲取各鍵空間的元數(shù)據(jù)
        if (self.prepared) {
            for (AVMetadataItem *item in self.asset.commonMetadata) {       
                //NSLog(@"%@: %@", item.keyString, item.value);

            }

            for (NSString *format in self.asset.availableMetadataFormats) { // 查詢此資源中所包含的所有元數(shù)據(jù)格式
                if ([acceptedFormats containsObject:format]) {  // 是否支持此格式.
                    NSArray *items = [self.asset metadataForFormat:format]; // 訪問對應(yīng)格式元數(shù)據(jù).
                    for (AVMetadataItem *item in items) {
                        //NSLog(@"%@: %@", item.keyString, item.value);
            
                    }
                }
            }
        }
    }];
}

AVMetadataItem 最基本形式是一個(gè)鍵值對的容器.可以通過它查詢key或commonKey來訪問元數(shù)據(jù). 但是它的key屬性是以id<NSObject,NSCopying>值的形式定義的,雖然可以保存NSString,但通過上面的打印可以知道Key只是無意義的整數(shù).所以我們最好添加一個(gè)AVMetadataItem分類方法,用來轉(zhuǎn)換獲取key的內(nèi)容.代碼如下:

@interface AVMetadataItem (THAdditions)
@property (readonly) NSString *keyString;

// .m
- (NSString *)keyString {
    if ([self.key isKindOfClass:[NSString class]]) {                        
        return (NSString *)self.key;
    } else if ([self.key isKindOfClass:[NSNumber class]]) {
        UInt32 keyValue = [(NSNumber *) self.key unsignedIntValue];         

         //大部分keys 有 4 字符長度,而 ID3v2.2 格式的keys 只有3個(gè)字符,下面代碼表示移動(dòng)每個(gè)字節(jié)來確定length長度是要截短.
        size_t length = sizeof(UInt32);                                     
        if ((keyValue >> 24) == 0) --length;
        if ((keyValue >> 16) == 0) --length;
        if ((keyValue >> 8) == 0) --length;
        if ((keyValue >> 0) == 0) --length;
        
        long address = (unsigned long)&keyValue;
        address += (sizeof(UInt32) - length);

        // keys 是以big-endian(高位優(yōu)先)格式存儲的.需要轉(zhuǎn)換成符合主流CPU順序的little-endian格式. 
        keyValue = CFSwapInt32BigToHost(keyValue);                         

        // 創(chuàng)建一個(gè)字符數(shù)組,以keys字符字節(jié)填充
        char cstring[length];                                               
        strncpy(cstring, (char *) address, length);
        cstring[length] = '\0';

        // 大部分QuickTime和iTunes keys前綴都有一個(gè) '?', 而AVMetadataFormat.h 用'@' 表示,所以轉(zhuǎn)換一下.
        if (cstring[0] == '\xA9') {                                         
            cstring[0] = '@';
        }

        return [NSString stringWithCString:(char *) cstring                 
                                  encoding:NSUTF8StringEncoding];
    }
    else {
        return @"<<unknown>>";
    }
}

除了通過鍵和鍵空間獲取資源的元數(shù)據(jù)之外,iOS 8之后添加了用identifier獲取元數(shù)據(jù)的方法. 標(biāo)識符將鍵和鍵空間統(tǒng)一成單獨(dú)的字符串,以一個(gè)更簡單的模型來獲取資源的元數(shù)據(jù),具體參考AVMetadataItem.h, 這里使用鍵和鍵空間是為了方便兼容以前的系統(tǒng).

1.3元數(shù)據(jù)的解析

通過上面的方法獲得元數(shù)據(jù)以及keys屬性的內(nèi)容轉(zhuǎn)換之后,接下來到了最難的部分,就是理解key對應(yīng)value中的數(shù)據(jù).當(dāng)value是一個(gè)簡單字符串時(shí),比如歌手或唱片名稱或年份,這樣容易理解的是不需要轉(zhuǎn)換的.但是有很多復(fù)雜key的value需要轉(zhuǎn)換解析:

  • Artwork:
    元數(shù)據(jù)Artwork對應(yīng)的value會以多種不同的格式返回,比如封面和海報(bào)等,它保存在一個(gè)NSData中,我們要先定位
// 解析
- (id)displayValueFromMetadataItem:(AVMetadataItem *)item {
    NSImage *image = nil;
    if ([item.value isKindOfClass:[NSData class]]) {       
        image = [[NSImage alloc] initWithData:item.dataValue];
    }
    else if ([item.value isKindOfClass:[NSDictionary class]]) {   // 如果對象是MP3格式.value就可能是個(gè)字典.
        NSDictionary *dict = (NSDictionary *)item.value;
        image = [[NSImage alloc] initWithData:dict[@"data"]];
    }
    return image;
}
// 恢復(fù)原格式
- (AVMetadataItem *)metadataItemFromDisplayValue:(id)value
                                withMetadataItem:(AVMetadataItem *)item {

    AVMutableMetadataItem *metadataItem = [item mutableCopy];

    NSImage *image = (NSImage *)value;
    metadataItem.value = image.TIFFRepresentation;                          
    return metadataItem;
}
  • 注釋:
    當(dāng)處理對象是MPEG-4或QuickTime時(shí),可以直接獲取對應(yīng)字符串,如果是mp3格式:
// 解析
- (id)displayValueFromMetadataItem:(AVMetadataItem *)item {

    NSString *value = nil;
    if ([item.value isKindOfClass:[NSString class]]) {                      // 1
        value = item.stringValue;
    }
    else if ([item.value isKindOfClass:[NSDictionary class]]) {             // 2
        NSDictionary *dict = (NSDictionary *) item.value;
        if ([dict[@"identifier"] isEqualToString:@""]) {
            value = dict[@"text"];
        }
    }
    return value;
}
// 恢復(fù)
- (AVMetadataItem *)metadataItemFromDisplayValue:(id)value
                                withMetadataItem:(AVMetadataItem *)item {

    AVMutableMetadataItem *metadataItem = [item mutableCopy];               // 3
    metadataItem.value = value;
    return metadataItem;
}
  • 音軌數(shù)據(jù)信息:
    音軌通常包含一首歌在整個(gè)唱片中的編號位置信息.
// 解析
- (id)displayValueFromMetadataItem:(AVMetadataItem *)item {
    
    NSNumber *number = nil;
    NSNumber *count = nil;
    
    if ([item.value isKindOfClass:[NSString class]]) {  // MP3音頻信息以"xx/xx"字符串格式返回.例如一個(gè)包含10首歌曲的唱片中第8首就是8/10;
        NSArray *components =
            [item.stringValue componentsSeparatedByString:@"/"];
        number = @([components[0] integerValue]);
        count = @([components[1] integerValue]);
    }
    else if ([item.value isKindOfClass:[NSData class]]) {  // 對于MPEG-4格式,則復(fù)雜點(diǎn).
        NSData *data = item.dataValue;
        if (data.length == 8) {
            uint16_t *values = (uint16_t *) [data bytes];
            if (values[1] > 0) {
                number = @(CFSwapInt16BigToHost(values[1]));               
            }
            if (values[2] > 0) {
                count = @(CFSwapInt16BigToHost(values[2]));                 
            }
        }
    }
    
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];           
    [dict setObject:number ?: [NSNull null] forKey:THMetadataKeyTrackNumber]; // 得到的音軌編號
    [dict setObject:count ?: [NSNull null] forKey:THMetadataKeyTrackCount];// 得到的音軌計(jì)數(shù)

    return dict;
}

// 恢復(fù)
- (AVMetadataItem *)metadataItemFromDisplayValue:(id)value
                                withMetadataItem:(AVMetadataItem *)item {

    AVMutableMetadataItem *metadataItem = [item mutableCopy];

    NSDictionary *trackData = (NSDictionary *)value;
    NSNumber *trackNumber = trackData[THMetadataKeyTrackNumber];
    NSNumber *trackCount = trackData[THMetadataKeyTrackCount];

    uint16_t values[4] = {0};                                                // 6
    
    if (trackNumber && ![trackNumber isKindOfClass:[NSNull class]]) {
        values[1] = CFSwapInt16HostToBig([trackNumber unsignedIntValue]);   // 7
    }
    
    if (trackCount && ![trackCount isKindOfClass:[NSNull class]]) {
        values[2] = CFSwapInt16HostToBig([trackCount unsignedIntValue]);    // 8
    }
    
    size_t length = sizeof(values);
    metadataItem.value = [NSData dataWithBytes:values length:length];       // 9

    return metadataItem;
}
  • 風(fēng)格信息:
    鄉(xiāng)村,爵士,藍(lán)調(diào)等等.
// 轉(zhuǎn)換
- (id)displayValueFromMetadataItem:(AVMetadataItem *)item {

    THGenre *genre = nil;

    if ([item.value isKindOfClass:[NSString class]]) {                      // 1
        if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3]) {
            // ID3v2.4 stores the genre as an index value
            if (item.numberValue) {                                         // 2
                NSUInteger genreIndex = [item.numberValue unsignedIntValue];
                genre = [THGenre id3GenreWithIndex:genreIndex];
            } else {
                genre = [THGenre id3GenreWithName:item.stringValue];        // 3
            }
        } else {
            genre = [THGenre videoGenreWithName:item.stringValue];          // 4
        }
    }
    else if ([item.value isKindOfClass:[NSData class]]) {                   // 5
        NSData *data = item.dataValue;
        if (data.length == 2) {
            uint16_t *values = (uint16_t *)[data bytes];
            uint16_t genreIndex = CFSwapInt16BigToHost(values[0]);
            genre = [THGenre iTunesGenreWithIndex:genreIndex];
        }
    }
    return genre;
}
//恢復(fù)
- (AVMetadataItem *)metadataItemFromDisplayValue:(id)value
                                withMetadataItem:(AVMetadataItem *)item {

    AVMutableMetadataItem *metadataItem = [item mutableCopy];

    THGenre *genre = (THGenre *)value;

    if ([item.value isKindOfClass:[NSString class]]) {                      // 6
        metadataItem.value = genre.name;
    }
    else if ([item.value isKindOfClass:[NSData class]]) {                   // 7
        NSData *data = item.dataValue;
        if (data.length == 2) {
            uint16_t value = CFSwapInt16HostToBig(genre.index + 1);         // 8
            size_t length = sizeof(value);
            metadataItem.value = [NSData dataWithBytes:&value length:length];
        }
    }

    return metadataItem;
}
1.4 導(dǎo)出修改后的元數(shù)據(jù)

通過上面的解析轉(zhuǎn)換方法,我們就可以進(jìn)行元數(shù)據(jù)信息的讀取與修改,修改完當(dāng)然需要保存了.不過中間還有一個(gè)問題: 由于AVAsset是一個(gè)不可變類,所以我們不能直接修改AVAsset,而是使用AVAssetExportSession類導(dǎo)出一個(gè)新的資源副本.

  • AVAssetExportSession配置
    AVAssetExportSession是用于將AVAsset內(nèi)容根據(jù)預(yù)設(shè)的導(dǎo)出條件進(jìn)行轉(zhuǎn)碼,并寫入磁盤中,用它可以實(shí)現(xiàn)將一種格式轉(zhuǎn)換成另一種格式,修訂資源內(nèi)容,修改資源的音視頻行為.也包含了寫入新的元數(shù)據(jù).
    所以創(chuàng)建AVAssetExportSession實(shí)例要先提供資源和預(yù)設(shè)條件; 導(dǎo)出預(yù)設(shè)用于確定導(dǎo)出內(nèi)容的質(zhì)量,大小等屬性. 創(chuàng)建完成后還需要指定一個(gè)outputURL寫入地址,并且給outputFileType一個(gè)格式.代碼如下:
- (void)saveWithCompletionHandler:(THCompletionHandler)handler {
  // 先用AVAssetExportPresetPassthrough預(yù)設(shè)值創(chuàng)建一個(gè)AVAssetExportSession
    NSString *presetName = AVAssetExportPresetPassthrough;                  
    AVAssetExportSession *session =
        [[AVAssetExportSession alloc] initWithAsset:self.asset
                                         presetName:presetName];
  // 配置導(dǎo)出預(yù)設(shè)
    NSURL *outputURL = [self tempURL];                                      
    session.outputURL = outputURL;
    session.outputFileType = self.filetype;
  // 用上面提到的解析與恢復(fù)方法修改元數(shù)據(jù)并返回
    session.metadata = [self.metadata metadataItems];                       

  // 最后異步導(dǎo)出修改后的元數(shù)據(jù)
    [session exportAsynchronouslyWithCompletionHandler:^{
        AVAssetExportSessionStatus status = session.status;
        BOOL success = (status == AVAssetExportSessionStatusCompleted);
        if (success) {                                                      // 4
            NSURL *sourceURL = self.url;
            NSFileManager *manager = [NSFileManager defaultManager];
            [manager removeItemAtURL:sourceURL error:nil];
            [manager moveItemAtURL:outputURL toURL:sourceURL error:nil];
        }
        
        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(success);
            });
        }
    }];
}

AVAssetExportPresetPassthrough預(yù)設(shè)值允許修改預(yù)設(shè)值,但是不能用于添加元數(shù)據(jù),如果要添加元數(shù)據(jù),需要使用轉(zhuǎn)碼預(yù)設(shè)值.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末振惰,一起剝皮案震驚了整個(gè)濱河市这弧,隨后出現(xiàn)的幾起案子捷凄,更是在濱河造成了極大的恐慌忱详,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跺涤,死亡現(xiàn)場離奇詭異匈睁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)桶错,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門航唆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牛曹,你說我怎么就攤上這事佛点〈祭模” “怎么了黎比?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵超营,是天一觀的道長。 經(jīng)常有香客問我阅虫,道長演闭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任颓帝,我火速辦了婚禮米碰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘购城。我一直安慰自己吕座,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布瘪板。 她就那樣靜靜地躺著吴趴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侮攀。 梳的紋絲不亂的頭發(fā)上锣枝,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音兰英,去河邊找鬼撇叁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛畦贸,可吹牛的內(nèi)容都是我干的陨闹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼薄坏,長吁一口氣:“原來是場噩夢啊……” “哼正林!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颤殴,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤觅廓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后涵但,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杈绸,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年矮瘟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞳脓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澈侠,死狀恐怖劫侧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤烧栋,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布写妥,位于F島的核電站,受9級特大地震影響审姓,放射性物質(zhì)發(fā)生泄漏珍特。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一魔吐、第九天 我趴在偏房一處隱蔽的房頂上張望扎筒。 院中可真熱鬧,春花似錦酬姆、人聲如沸嗜桌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽症脂。三九已至,卻和暖如春淫僻,著一層夾襖步出監(jiān)牢的瞬間诱篷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工雳灵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棕所,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓悯辙,卻偏偏與公主長得像琳省,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子躲撰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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