iOS中的视频播放

发布时间:2024年01月20日

引言

在数字时代,视频已成为我们日常沟通和娱乐不可或缺的一部分。从简短的社交媒体剪辑到全长电影,我们对流畅、高质量视频播放的需求从未如此强烈。对于开发者来说,为iOS用户提供无瑕疵的视频体验是一项挑战,也是一种艺术。本篇博客将带你深入了解苹果生态系统中视频播放的核心——AV Kit和AV Foundation。

AV Kit

我们将首先探讨AV Kit的强大功能,这是一个为iOS开发者设计的高级视频播放框架。它封装了复杂的底层技术,提供了易于使用的界面,允许你迅速地集成视频播放功能到你的应用中。从管理播放控制和状态,到处理各种屏幕大小和设备方向,AV Kit让一切变得简单。

介绍

AV Kit从iOS 8开始被引入到iOS平台,是一个非常简单的标准框架,只包含了一个AVPlayerViewContoller类。它继承自UIViewController,用于展示和控制AVPlayer实例的播放。AVPlayerViewController具有一个很简单的界面,并提供了许多属性,下面列举几个常用的属性:

  • player:用来播放媒体内容的AVPlayer实例。
  • showsPlayebackControls:用来表示播放器中控制控件是否显示。
  • videoGravity:设置视频的填充方式。
  • readyForDisplay:通过观察这个值来确定视频内容是否已经准备好进行播放。

实现

上面的页面几乎已经包括了视频播放的所有功能,播放、暂停、快进、音量,甚至还支持了AirPlay,我们来看一下它的实现有多么简单。

 NSURL * url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@".mov"];
    AVPlayerViewController * playerViewController = [[AVPlayerViewController alloc] init];
    playerViewController.player = [[AVPlayer alloc] initWithURL:url];
    playerViewController.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:playerViewController animated:YES completion:nil];

AV Foundation

然而,每一位热衷于精细控制和定制播放体验的开发者都知道,有时“标准”是不够的。这就是我们转向AV Foundation的地方。在这个强大的框架下,你将揭开视频播放的神秘面纱,学习如何构建一个高级播放控制的完全自定义播放器。无论你是想要优化性能、处理复杂的用户交互,还是只是为了给你的应用那独特的个性化触摸,AV Foundation都为你提供了必要的工具。

介绍

使用AV Foundation实现视频播放的方式,最终是通过使用AVPlayer来播放AVAsset,但是AVAsset模型只包含媒体资源的静态信息,这些不变的属性用来描述对象的静态状态。这就意味着仅适用AVAsset对象时无法实现播放功能的。当我们需要对一个资源及其相关曲目进行播放时,首先需要通过AVPlayerItem和AVPlayerItemTrack类构件相应的动态内容。

AVAsset

AVAsset是一个抽象类和不可变类,定义了媒体资源混合呈现的方式,将媒体资源的静态属性模块化成一个整体,比如它们的标题、时长和元数据等。

AVPlayerItem

AVPlayerItem会建立媒体资源动态视角的数据模型并保存AVPlayer在播放资源时的呈现状态。在这个类中我们会看到诸如seekToTime:的方法以及访问currentTime和presentationSize的属性。

AVPlayer

AVPlayer是一个用来播放基于时间的视听媒体的控制器对象。支持播放本地、分步下载或通过HTTP Live Streaming协议得到的流媒体,并在多种播放场景中播放这些视频资源。

AVPlayerLayer

AVPlayerLayer构建于Core Animation之上,AVPlayerLayer扩展了Core Animation的CALayer类,并通过框架在屏幕上显示视频内容。这一图层并不提供任何可视化空间或其他附件(根据开发者需求搭建的),但是它用作是内容的渲染面。创建AVPlayerLayer需要一个指向AVPlayer实例的指针,这就将图层和播放器紧密绑定在一起,保证了当播放器基于时间的方法出现时使二者保持同步。

简单实现

使用上面的关键类,实现一个非常简单的视频播放功能,获取资源,播放资源,将播放图层添加到当前控制器图层中。

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property(nonatomic,strong)AVPlayer * player;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //1.Define the asset URL
    NSURL * assetURL = [[NSBundle mainBundle] URLForResource:@"waves" withExtension:@"mp4"];
    //2.Create an instance of AVAsset
    AVAsset * asset = [AVAsset assetWithURL:assetURL];
    //3.Create an AVPlayerItem with a pointer to the asset to play
    AVPlayerItem * playerItem = [AVPlayerItem playerItemWithAsset:asset];
    //4.Create an instance of AVPlayer with a pointer to the player item
    self.player = [AVPlayer playerWithPlayerItem:playerItem];
    //5.create a player layer to driect the video content
    AVPlayerLayer * playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    //6.attach layer into layer hierarchy
    [self.view.layer addSublayer:playerLayer];
}

完整实现

下面我们使用以上的关键类,来实现一个相对较为负责,功能较为全面的播放器,需要包括播放,暂停,播放进度显示,已经拖拽进度条等功能。

我们将实现分散到三个不同类当中THTransport,THOverlayView,THPlayerView,THPlayerController。

THTransport

代理,接口如下:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol THTransportDelegate <NSObject>

- (void)play;
- (void)pause;
- (void)stop;

- (void)scrubbingDidStart;
- (void)scrubbedToTime:(NSTimeInterval)time;
- (void)scrubbingDidEnd;
- (void)jumpedToTime:(NSTimeInterval)time;

@end

@protocol THTransport <NSObject>

@property(nonatomic,weak)id <THTransportDelegate>  delegate;

- (void)setTitle:(NSString *)title;
- (void)setCurrentTime:(NSTimeInterval)time duration:(NSTimeInterval)duration;
- (void)setScrubbingTime:(NSTimeInterval)time;
- (void)playbackComplete;

@end

NS_ASSUME_NONNULL_END
THOverlayView

与用户交互的视图图层,上面绘制了播放,暂停按钮, 进度条,时间显示等等。接口如下

#import <UIKit/UIKit.h>
#import "THTransport.h"

NS_ASSUME_NONNULL_BEGIN

@interface THOverlayView : UIView<THTransport>

@property(nonatomic,weak)id <THTransportDelegate>  delegate;

- (void)setCurrentTime:(NSTimeInterval)time;


@end

NS_ASSUME_NONNULL_END

遵循了THTransport协议,并且有一个遵循THTransportDelegate的delegate属性,和设置当前时间的方法。

实现如下:

#import "THOverlayView.h"
#import "UIView+THAdditions.h"

@interface THOverlayView ()

@property(nonatomic,assign)BOOL  controlsHidden;
@property(nonatomic,strong)UINavigationBar * navBar;
@property(nonatomic,strong)UIButton * showButton;
@property(nonatomic,strong)UIToolbar * toolBar;
@property(nonatomic,strong)UIButton * playButton;
@property(nonatomic,strong)UISlider * slider;
@property(nonatomic,strong)UILabel * currentTimeLabel;
@property(nonatomic,strong)UILabel * durationTimeLabel;
@property(nonatomic,assign)BOOL  scrubbing;
@property(nonatomic,assign)CGFloat  lastPlaybackRate;

@property(nonatomic,strong)NSTimer * timer;

@end

@implementation THOverlayView


- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupView];
    }
    return self;
}

- (void)setupView{
    [self addNavBar];
    [self addToolBar];
    [self addGestureRecognizer];
    [self resetTimer];
}


- (void)addNavBar{
    self.navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 44)];
    [self addSubview:self.navBar];
    
    UINavigationItem * navigationItem = [[UINavigationItem alloc] init];
    UIBarButtonItem * downItemButton = [[UIBarButtonItem alloc] initWithTitle:@"Down" style:UIBarButtonItemStyleDone target:self action:@selector(down)];
    self.showButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 20)];
    [self.showButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.showButton setTitle:@"Show" forState:UIControlStateNormal];
    [self.showButton setTitle:@"Hide" forState:UIControlStateSelected];
    [self.showButton addTarget:self action:@selector(showOnclick:) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem * showItemButton = [[UIBarButtonItem alloc] initWithCustomView:self.showButton];
    navigationItem.leftBarButtonItem = downItemButton;
    navigationItem.rightBarButtonItem = showItemButton;
    [self.navBar pushNavigationItem:navigationItem animated:YES];
}

- (void)addToolBar{
    self.toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, [UIScreen mainScreen].bounds.size.height - 44, [UIScreen mainScreen].bounds.size.width, 44)];
    [self addSubview:self.toolBar];
    UIBarButtonItem * spaceItem0 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    
    self.playButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 33, 33)];
    self.playButton.selected = YES;
    [self.playButton setImage:[UIImage imageNamed:@"play_button"] forState:UIControlStateNormal];
    [self.playButton setImage:[UIImage imageNamed:@"pause_button"] forState:UIControlStateSelected];
    [self.playButton addTarget:self action:@selector(playButtonOnclick:) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem * playItem = [[UIBarButtonItem alloc] initWithCustomView:self.playButton];
    
    UIBarButtonItem * spaceItem1 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    
    self.currentTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 80, 20)];
    self.currentTimeLabel.textAlignment = NSTextAlignmentCenter;
    self.currentTimeLabel.font = [UIFont systemFontOfSize:15];
    self.currentTimeLabel.textColor = [UIColor darkTextColor];
    self.currentTimeLabel.text = @"00:00";
    UIBarButtonItem * currentItem = [[UIBarButtonItem alloc] initWithCustomView:self.currentTimeLabel];
    
    self.slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width - 290, 0)];
    [self.slider addTarget:self action:@selector(showPopupUI) forControlEvents:UIControlEventValueChanged];
    [self.slider addTarget:self action:@selector(hidePopupUI) forControlEvents:UIControlEventTouchUpInside];
    [self.slider addTarget:self action:@selector(unhidePopupUI) forControlEvents:UIControlEventTouchDown];
    UIBarButtonItem * sliderItem = [[UIBarButtonItem alloc] initWithCustomView:self.slider];
    
    self.durationTimeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 80, 20)];
    self.durationTimeLabel.textAlignment = NSTextAlignmentCenter;
    self.durationTimeLabel.font = [UIFont systemFontOfSize:15];
    self.durationTimeLabel.textColor = [UIColor darkTextColor];
    self.durationTimeLabel.text = @"00:00";
    UIBarButtonItem * durationItem = [[UIBarButtonItem alloc] initWithCustomView:self.durationTimeLabel];
    
    UIBarButtonItem * spaceItem2 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    
    UIBarButtonItem * subtitlesItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"subtitles"] style:UIBarButtonItemStyleDone target:self action:@selector(showSubtitleViewController)];
    UIBarButtonItem * spaceItem3 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    
    self.toolBar.items = @[spaceItem0,playItem,spaceItem1,currentItem,sliderItem,durationItem,spaceItem2,subtitlesItem,spaceItem3];
}

- (void)addGestureRecognizer{
    self.userInteractionEnabled = YES;
    UITapGestureRecognizer * tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(recognizer)];
    [self addGestureRecognizer:tapGestureRecognizer];
}

//MARK:播放按钮
- (void)playButtonOnclick:(UIButton *)button{
    button.selected = !button.selected;
    if (self.delegate) {
        if (button.selected) {
            [self.delegate play];
        }else{
            [self.delegate pause];
        }
    }
}


//MARK:按下搓擦条
- (void)unhidePopupUI{
    self.scrubbing = YES;
    [self resetTimer];
    [self.delegate scrubbingDidStart];
}

//MARK:搓擦条值改变
- (void)showPopupUI{
    self.currentTimeLabel.text = @"--:--";
    [self setScrubbingTime:self.slider.value];
    [self.delegate scrubbedToTime:self.slider.value];
}

//MARK:手指离开搓擦条
- (void)hidePopupUI{
    self.scrubbing = NO;
    [self.delegate scrubbingDidEnd];
}

//MARK:点击屏幕显示或隐藏控制栏
- (void)recognizer{
    [UIView animateWithDuration:0.35 animations:^{
        if (!self.controlsHidden) {
                self.navBar.frameY -= self.navBar.frameHeight;
                self.toolBar.frameY += self.toolBar.frameHeight;
        }else{
            self.navBar.frameY += self.navBar.frameHeight;
            self.toolBar.frameY -= self.toolBar.frameHeight;
            [self resetTimer];
        }
        self.controlsHidden = !self.controlsHidden;
    }];
}

//MARK:重新开始定时器
- (void)resetTimer{
    [self.timer invalidate];
    if (!self.scrubbing) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:5 repeats:NO block:^(NSTimer * _Nonnull timer) {
            if (self.timer.isValid && !self.controlsHidden) {
                [self recognizer];
            }
        }];
    }
}

//MARK:指定时间播放
- (void)setCurrentTime:(NSTimeInterval)time{
    [self.delegate jumpedToTime:time];
}

//MARK:THTransport 设置时间及进度条
- (void)setCurrentTime:(NSTimeInterval)time duration:(NSTimeInterval)duration{
    NSInteger currentSeconds = ceilf(time);
//    double remainingTime = duration - time;
    self.currentTimeLabel.text = [self formatSeconds:currentSeconds];
    self.durationTimeLabel.text = [self formatSeconds:duration];
    self.slider.minimumValue = 0.0f;
    self.slider.maximumValue = duration;
    self.slider.value = time;
}

//MARK:THTransport 设置时间
- (void)setScrubbingTime:(NSTimeInterval)time{
    self.currentTimeLabel.text = [self formatSeconds:time];
}

//MARK:THTransport 播放完成
- (void)playbackComplete{
    self.slider.value = 0.0f;
    self.playButton.selected = NO;
}


//MARK:返回
- (void)down{
    [self.delegate stop];
    [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}

- (NSString *)formatSeconds:(NSInteger)value {
    NSInteger seconds = value % 60;
    NSInteger minutes = value / 60;
    return [NSString stringWithFormat:@"%02ld:%02ld", (long) minutes, (long) seconds];
}

@end
THPlayerView

播放视图,作用相当于是AVPlayerLayer,接口如下:

#import <UIKit/UIKit.h>
#import "THTransport.h"

@class AVPlayer;

NS_ASSUME_NONNULL_BEGIN

@interface THPlayerView : UIView

- (id)initWithPlayer:(AVPlayer *)player;

@property(nonatomic,readonly) id <THTransport>  transport;

@end

NS_ASSUME_NONNULL_END

接口比较简单,有一个自定义的初始化方法,和一个遵循THTransport的transport。

实现如下:

#import "THPlayerView.h"
#import <AVFoundation/AVFoundation.h>
#import "THOverlayView.h"

@interface THPlayerView ()

@property(nonatomic,strong)THOverlayView * overlayView;

@end

@implementation THPlayerView

+ (Class)layerClass{
    return [AVPlayerLayer class];
}

- (id)initWithPlayer:(AVPlayer *)player{
    self = [super initWithFrame:CGRectZero];
    if (self) {
        self.backgroundColor = [UIColor blackColor];
        self.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        [(AVPlayerLayer *)[self layer] setPlayer:player];
        [self addOverlayView];
    }
    return self;
}

- (void)addOverlayView{
    self.overlayView = [[THOverlayView alloc] initWithFrame:CGRectZero];
    [self addSubview:self.overlayView];
}

- (void)layoutSubviews{
    [super layoutSubviews];
    self.overlayView.frame = self.bounds;
}

- (id <THTransport>)transport{
    return self.overlayView;
}


@end

THPlayerController

控制器,但不是视图控制器,主要负责播放器的播放,暂停,快进等功能,是我们处理核心播放API方法的地方。

创建控制器

该类的接口比较简单,只有一个初始化方法和一个只读的view属性。

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface THPlayerController : NSObject
- (id)initWithURL:(NSURL *)assetURL;
@property(nonatomic,strong,readonly)UIView * view;
@end
NS_ASSUME_NONNULL_END

因为它是一个比较核心的类,所以实现较为复杂,我们可以差分开来进行说明,首先我们来看一下它的扩展

#import "THPlayerController.h"
#import <AVFoundation/AVFoundation.h>
#import "THTransport.h"
#import "THPlayerView.h"
#define STATUS_KEYPATH @"status"
#define REFESH_INTERVAL 0.5f
static const NSString * PlayerItemStatusContext;
@interface THPlayerController ()<THTransportDelegate>
@property(nonatomic,strong)AVAsset * asset;
@property(nonatomic,strong)AVPlayerItem * playerItem;
@property(nonatomic,strong)AVPlayer * player;
@property(nonatomic,strong)THPlayerView * playerView;
@property(nonatomic,weak)id <THTransport> transport;
@property(nonatomic,strong)id timeObserver;
@property(nonatomic,strong)id itemEndObserver;
@property(nonatomic,assign)float  lastPlaybackRate;
@end

里面定义了前三个属性,就是我们提到的关键类中的三个,而THPlayerView上面我们已经提到是用来显示播放画面的一个视图。

接下来看一下它的实现

@implementation THPlayerController
- (id)initWithURL:(NSURL *)assetURL{
    self = [super init];
    if (self) {
        _asset = [AVAsset assetWithURL:assetURL];
        [self prepareToPlay];
    }
    return self;
}
- (void)prepareToPlay{
    NSArray * keys = @[@"tracks",@"duration",@"commonMetadata"];
    self.playerItem = [AVPlayerItem playerItemWithAsset:self.asset automaticallyLoadedAssetKeys:keys];
    [self.playerItem addObserver:self
                      forKeyPath:STATUS_KEYPATH
                         options:0
                         context:&PlayerItemStatusContext];
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    self.playerView = [[THPlayerView alloc] initWithPlayer:self.player];
    self.transport = self.playerView.transport;
    self.transport.delegate = self;
}

首先是一个自定义的初始化方法,传入资源的URL,开始准备播放的播放工作。

创建AVPlayerItem的时候使用新的初始化方法initWithAsset:automaticallyLoadedAssetKeys:方法并传入了一个keys作为参数,使用这个方法会让AVPlayerItem在初始化队列过程中载入tracks、duration和commonMetadata属性。

监听添AVPlayerItem的status属性,当它转换为AVPlayerItemStatusReadyToPlay时开始执行播放。

监听状态改变

下面我们来看一下监听状态的实现

//MARK:监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if (context == &PlayerItemStatusContext) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.playerItem removeObserver:self forKeyPath:STATUS_KEYPATH];
            if (self.playerItem.status == AVPlayerItemStatusReadyToPlay) {
                //设置时间监听器
                [self addPlayerItemTimeObserver];
                [self addItemEndObserverForPlayerItem];
                CMTime duration = self.playerItem.duration;
                //同步显示时间
                [self.transport setCurrentTime:CMTimeGetSeconds(kCMTimeZero) duration:CMTimeGetSeconds(duration)];
                [self.player play];
            }else{
                NSLog(@"加载视频失败");
            }
        });
    }
}
进度监听

AVPlayer提供了一个addPeriodicTimeObserverForInterval:queue:usingBlock:方法来让我们实现播放进度的监听。

- (void)addPlayerItemTimeObserver{
    //创建0.5秒刷新
    CMTime interval = CMTimeMakeWithSeconds(REFESH_INTERVAL, NSEC_PER_SEC);
    //主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    __weak  THPlayerController * weakSelf = self;
    //添加观察
    self.timeObserver = [self.player addPeriodicTimeObserverForInterval:interval
                                                                  queue:queue
                                                             usingBlock:^(CMTime time) {
        NSTimeInterval currentTime = CMTimeGetSeconds(time);
        NSTimeInterval duration = CMTimeGetSeconds(weakSelf.playerItem.duration);
        [weakSelf.transport setCurrentTime:currentTime duration:duration];
    }];
}
播放结束
- (void)addItemEndObserverForPlayerItem{
    NSString * name = AVPlayerItemDidPlayToEndTimeNotification;
    NSOperationQueue * queue = [NSOperationQueue mainQueue];
    __weak THPlayerController * weakSelf = self;
    self.itemEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:name
                                                                             object:self.playerItem
                                                                              queue:queue usingBlock:^(NSNotification * _Nonnull note) {
        [weakSelf.player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
            [weakSelf.transport playbackComplete];
        }];
    }];
}
- (void)dealloc{
    if (self.itemEndObserver) {
        NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
        [nc removeObserver:self.itemEndObserver
                      name:AVPlayerItemDidPlayToEndTimeNotification
                    object:self.player.currentItem];
        self.itemEndObserver = nil;
    }
}
代理方法

//MARK:播放
- (void)play {
    [self.player play];
}
//MARK:暂停
- (void)pause {
    self.lastPlaybackRate = self.player.rate;
    [self.player pause];
}
//MARK:停止
- (void)stop {
    [self.player setRate:0.0f];
    [self.transport playbackComplete];
}
//MARK:跳转到
- (void)jumpedToTime:(NSTimeInterval)time {
    [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
}
- (void)scrubbingDidStart {
    self.lastPlaybackRate = self.player.rate;
    [self.player pause];
    [self.player removeTimeObserver:self.timeObserver];
}
//MARK:拖拽结束
- (void)scrubbingDidEnd {
    [self addPlayerItemTimeObserver];
    if (self.lastPlaybackRate > 0.0f) {
        [self.player play];
    }
}
//MARK:拖拽到
- (void)scrubbedToTime:(NSTimeInterval)time {
    [self.playerItem cancelPendingSeeks];
    [self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)];
}

结语

本博客将通过分析两个框架的特点和区别,带你走在打造完美iOS视频播放体验的前沿。我们将从AV Kit的简单集成开始,然后深入AV Foundation的定制深渊,揭示那些令你的应用在竞争激烈的App Store中脱颖而出的秘密。

文章来源:https://blog.csdn.net/weixin_39339407/article/details/135471951
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。