iOS swfit 利用AVPlayer自定義播放器播放網(wǎng)絡(luò)視頻

今天我們來聊聊AVPlayer,這是一個AVFoundation庫里面的類绅喉,可以用來播放網(wǎng)絡(luò)視頻使用渠鸽。

開始正題之前,我們首先來了解幾個我們將要使用的對象:

AVPlayerItem :媒體資源管理對象柴罐,管理視頻的一些基本信息和狀態(tài)拱绑,如 播放進度、緩存進度等 丽蝎。 一個AVPlayerItem對應(yīng)著一個視頻資源。
AVPlayer :視頻操作對象,自己本身無法顯示視頻屠阻,需要把自己添加到一個AVPlayerLayer 上來操作红省。
AVPlayerLayer: 用來顯示視頻。

了解之后国觉,我們進入正題吧恃。
首先我們創(chuàng)建一個CAplayerView繼承于UIView,在這個自定義view上完成一些操作麻诀。分析一下痕寓,需要包含哪些東西,比如說UI界面蝇闭,AVPlayer配置,通知,KVO他托,各種手勢等祸憋。我們一個一個來實現(xiàn)。

首先我們?yōu)镃AplayerView寫一個便利構(gòu)造器:

  convenience  init(frame: CGRect,theUrl:URL) {  
        self.init(frame: frame)
        url = theUrl      //視頻url
        setupUI()        //UI界面
        setupTap()      //手勢
        setupPlayer()       //avplayer
        link = CADisplayLink(target: self, selector: #selector(update))
        link.add(to: RunLoop.main, forMode: .defaultRunLoopMode)   //定時器
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
UI界面

定義我們需要的控件

  var timeLabel:UILabel!  //視頻時間
    var slider:UISlider!    //視頻進度條
    var sliding = false
    var progressView:UIProgressView!  //緩沖條
    var playBtn:UIButton!    //播放暫停按鈕
    var playing = true
    var backBtn:UIButton!    //返回按鈕
    var fullScreenBtn:UIButton!     //全屏按鈕
    var titleLabel:UILabel!   //標(biāo)題
func setupUI () {
        
        timeLabel = UILabel()
        timeLabel.textColor = UIColor.white
        timeLabel.font = UIFont.systemFont(ofSize: 12)
        self.addSubview(timeLabel)
        timeLabel.snp.makeConstraints { (make) in
            
            make.right.equalTo(self).inset(25)
            make.bottom.equalTo(self).inset(5)
            
        }
        
        fullScreenBtn = UIButton()
        self.addSubview(fullScreenBtn)
        fullScreenBtn.snp.makeConstraints { (make) in
            
            make.right.equalTo(self).inset(5)
            make.bottom.equalTo(self).inset(5)
            make.width.height.equalTo(15)
        }
        // 設(shè)置按鈕圖片
        fullScreenBtn.setImage(UIImage(named: "full_screen"), for: .normal)
        // 點擊事件
        fullScreenBtn.addTarget(self, action: #selector(tapChangeScreen), for: .touchUpInside)
        
        
        slider = UISlider()
        self.addSubview(slider)
        slider.snp.makeConstraints { (make) in
            make.bottom.equalTo(self).inset(5)
            make.left.equalTo(self).offset(50)
            make.right.equalTo(self).inset(100)
            make.height.equalTo(15)
        }
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 0
        // 從最大值滑向最小值時桿的顏色
        slider.maximumTrackTintColor = UIColor.clear
        // 從最小值滑向最大值時桿的顏色
        slider.minimumTrackTintColor = UIColor.white
        // 在滑塊圓按鈕添加圖片
        slider.setThumbImage(UIImage(named: "knob"), for: .normal)
        // 按下的時候
        slider.addTarget(self, action: #selector(sliderTouchDown(slider:)), for: .touchDown)
        // 彈起的時候
        slider.addTarget(self, action: #selector(sliderTouchUpOut(slider:)), for: .touchUpOutside)
        slider.addTarget(self, action: #selector(sliderTouchUpOut(slider:)), for: .touchUpInside)
        slider.addTarget(self, action: #selector(sliderTouchUpOut(slider:)), for: .touchCancel)
        
        progressView = UIProgressView()
        progressView.backgroundColor = UIColor.lightGray
        self.insertSubview(progressView, belowSubview: slider)
        progressView.snp.makeConstraints { (make) in
            make.left.right.equalTo(slider)
            make.centerY.equalTo(slider)
            make.height.equalTo(2)
        }
        
        progressView.tintColor = UIColor.red
        progressView.progress = 0
        
        playBtn = UIButton()
        self.addSubview(playBtn)
        playBtn.snp.makeConstraints { (make) in
            make.centerY.equalTo(slider)
            make.left.equalTo(self).offset(10)
            make.width.height.equalTo(30)
        }
        // 設(shè)置按鈕圖片
        playBtn.setImage(UIImage(named: "pause"), for: .normal)
        // 點擊事件
        playBtn.addTarget(self, action: #selector(playAndPause(btn:)), for: .touchUpInside)
        
        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name:NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
        
        
        backBtn = UIButton()
        self.addSubview(backBtn)
        backBtn.snp.makeConstraints { (make) in
            make.top.equalTo(self).offset(10)
            make.left.equalTo(self).offset(10)
            make.width.height.equalTo(30)
        }
        // 設(shè)置按鈕圖片
        backBtn.setImage(UIImage(named: "Back-white"), for: .normal)
        // 點擊事件
        backBtn.addTarget(self, action: #selector(onClickBackBtnAction), for: .touchUpInside)
        backBtn.isHidden = true
        
        titleLabel = UILabel()
        titleLabel.text = "這里顯示視頻的標(biāo)題"
        titleLabel.font = UIFont.systemFont(ofSize: 14)
        titleLabel.textColor = UIColor.white
        self.addSubview(titleLabel)
        titleLabel.snp.makeConstraints { (make) in
            
            make.top.equalTo(self).offset(10)
            make.height.equalTo(30)
            make.centerX.equalTo(self)
            
        }

    }
AVPlayer配置
 var playerLayer:AVPlayerLayer?
    var playerItem:AVPlayerItem!
    var player:AVPlayer!
    var url:URL?
func setupPlayer () {
        
        guard (url != nil) else {
            
            fatalError("連接錯誤")
        }
        
        playerItem = AVPlayerItem(url: url!)
        //監(jiān)聽緩沖進度改變
        playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
        // 監(jiān)聽狀態(tài)改變
        playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
        player = AVPlayer(playerItem: playerItem)
        player.volume = 0.5
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.videoGravity = .resizeAspectFill
        playerLayer?.contentsScale = UIScreen.main.scale
        self.layer.insertSublayer(playerLayer!, at: 0)
    }
    
    deinit {
        
        playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
        playerItem.removeObserver(self, forKeyPath: "status")
    }
添加手勢

因為Pan手勢功能不一樣逻悠,我們可以寫個枚舉來定義Pan手勢的類型

enum Direction {
    
    case leftOrRight,upOrDown,none
}
 var direction:Direction!   //pan手勢類型
 var isVolume = false    //是否為改變聲音手勢
 var oldConstriants:Array<NSLayoutConstraint>!     //舊的布局
 var isFullScreen:Bool!     //是否全屏
func setupTap () {
        
        let fullOrNotFullScreenTap = UITapGestureRecognizer(target: self, action: #selector(tapChangeScreen))
        fullOrNotFullScreenTap.numberOfTapsRequired = 2
        self.addGestureRecognizer(fullOrNotFullScreenTap)
        
        let disOrNotdisAppearTap = UITapGestureRecognizer(target: self, action: #selector(disOrNotDisAppear))
        disOrNotdisAppearTap.numberOfTapsRequired  = 1
        self.addGestureRecognizer(disOrNotdisAppearTap)
        
        //這行很關(guān)鍵元践,意思是只有當(dāng)沒有檢測到雙擊手勢 或者 檢測雙擊手勢失敗,s單擊手勢才有效
        disOrNotdisAppearTap.require(toFail: fullOrNotFullScreenTap)
        
        let pan  = UIPanGestureRecognizer(target: self, action: #selector(changeVoiceOrLightOrProgress(pan:)))
        self.addGestureRecognizer(pan)
        pan.delegate = self as? UIGestureRecognizerDelegate
        
        
    }
    
    @objc func changeVoiceOrLightOrProgress (pan:UIPanGestureRecognizer) {
        
        let offsetPoint = pan.translation(in: self)
        let locationPoint = pan.location(in: self)
        let veloctyPoint = pan.velocity(in: self)
        
        switch pan.state {
        case .began:
            let x = fabs(veloctyPoint.x)
            let y = fabs(veloctyPoint.y)
            if x > y {
                direction = .leftOrRight
            }
            else if x < y {
                
                direction = .upOrDown
                if locationPoint.x <= self.frame.size.width/2 {
                    
                    isVolume = false
                } else {
                    isVolume = true
                }
            }
            break
            
        case .changed:
            
            if direction == .upOrDown {
                
                if isVolume == false && offsetPoint.y > 0 {
                    
                    var newBrightness = UIScreen.main.brightness - 0.01
                    if newBrightness < 0 {
                        newBrightness = 0
                    }
                    UIScreen.main.brightness = newBrightness
                }
              else  if isVolume == false && offsetPoint.y < 0 {
                    
                    var newBrightness = UIScreen.main.brightness + 0.01
                    if newBrightness > 1 {
                        newBrightness = 1
                    }
                    UIScreen.main.brightness = newBrightness
                }
              else  if isVolume == true && offsetPoint.y > 0 {
                    
                    var newVolume = player.volume - 0.01
                    if newVolume < 0 {
                        newVolume = 0
                    }
                    player.volume = Float(newVolume)
                }
                else  if isVolume == true && offsetPoint.y < 0 {
                    
                    var newVolume = player.volume + 0.01
                    if newVolume > 1 {
                        newVolume = 1
                    }
                    player.volume = Float(newVolume)
                }
                
                
            }
            else if direction == .leftOrRight {
        
                //可在這里添加左右滑動改變視頻進度的代碼
            }
            
            break
            
        case .ended:
            
            if direction == .upOrDown {
                
                isVolume = false
            }
            else if direction == .leftOrRight {
                
            }
            
            break
            
        default:
            break
        }
        
        pan.setTranslation(CGPoint.zero, in: self)
        
    }
    
    @objc func tapChangeScreen () {
        
        if isFullScreen == false {
            
            let rotation : UIInterfaceOrientationMask = [.landscapeLeft, .landscapeRight]
            kAppdelegate?.blockRotation = rotation
        }  else {
            kAppdelegate?.blockRotation = .portrait
        }
        
    }
    
    @objc func disOrNotDisAppear () {
        
        if timeLabel.isHidden == false {
            
            timeLabel.isHidden = true
            slider.isHidden = true
            progressView.isHidden = true
            playBtn.isHidden = true
            backBtn.isHidden = true
            fullScreenBtn.isHidden = true
            titleLabel.isHidden = true
        }

        else if  timeLabel.isHidden == true && isFullScreen == true {
            
            timeLabel.isHidden = false
            slider.isHidden = false
            progressView.isHidden = false
            playBtn.isHidden = false
            backBtn.isHidden = false
            fullScreenBtn.isHidden = true
            titleLabel.isHidden = false
        }
            
        else if  timeLabel.isHidden == true && isFullScreen == false {
            
            timeLabel.isHidden = false
            slider.isHidden = false
            progressView.isHidden = false
            playBtn.isHidden = false
            backBtn.isHidden = true
            fullScreenBtn.isHidden = false
            titleLabel.isHidden = false
        }        
    }
KVO 通知 點擊事件以及定時器方法
 //MARK:----------KVO方法
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        if keyPath == "loadedTimeRanges" {
            
            // 通過監(jiān)聽AVPlayerItem的"loadedTimeRanges"童谒,可以實時知道當(dāng)前視頻的進度緩沖
            let loadedTime = avalableDurationWithplayerItem()
            let totalTime = CMTimeGetSeconds(playerItem.duration)
            let percent = loadedTime/totalTime // 計算出比例
            // 改變進度條
            progressView.progress = Float(percent)
            
        }
        else if keyPath == "status" {
            
            if playerItem.status == .readyToPlay {
                player.play()
            } else {
                print("加載異常")
            }
        }
    }
    
    func avalableDurationWithplayerItem()->TimeInterval{
        guard let loadedTimeRanges = player?.currentItem?.loadedTimeRanges,let first = loadedTimeRanges.first else {fatalError()}
        let timeRange = first.timeRangeValue
        let startSeconds = CMTimeGetSeconds(timeRange.start)
        let durationSecound = CMTimeGetSeconds(timeRange.duration)
        let result = startSeconds + durationSecound
        return result
    }
    
   
    
    //MARK:----------通知方法
    @objc func deviceOrientationDidChange() {
        
        let interfaceOrientation = UIApplication.shared.statusBarOrientation
        switch interfaceOrientation {
        case .landscapeLeft,.landscapeRight:
            
            timeLabel.isHidden = true
            slider.isHidden = true
            progressView.isHidden = true
            playBtn.isHidden = true
            backBtn.isHidden = true
            fullScreenBtn.isHidden = true
            titleLabel.isHidden = true
            
            isFullScreen = true
            oldConstriants = getCurrentVC().view.constraints
            self.updateConstraintsIfNeeded()
            //刪除UIView animate可以去除橫豎屏切換過渡動畫
            UIView.animate(withDuration: kTransitionTime, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: .transitionCurlUp, animations: {
                
                UIApplication.shared.keyWindow?.addSubview(self)
                self.snp.makeConstraints { (make) in
                    make.edges.equalTo(UIApplication.shared.keyWindow!)
                }
                self.layoutIfNeeded()
                
            }) { (bool) in
                
            }
            break
        case .portrait,.portraitUpsideDown:
            
            timeLabel.isHidden = false
            slider.isHidden = false
            progressView.isHidden = false
            playBtn.isHidden = false
            titleLabel.isHidden = false
            backBtn.isHidden = true
            fullScreenBtn.isHidden = false
            isFullScreen = false
            getCurrentVC().view.addSubview(self)
            UIView.animateKeyframes(withDuration: kTransitionTime, delay: 0, options: .calculationModeLinear, animations: {
                if (self.oldConstriants != nil) {
                    self.getCurrentVC().view.addConstraints(self.oldConstriants)
                }
            }, completion: nil)
            break
        case .unknown:
            print("UIInterfaceOrientationUnknown")
            break
        default:
            break
        }
        
        getCurrentVC().view.layoutIfNeeded()
        
    }
    
    func getCurrentVC()->UIViewController {
        
        var result:UIViewController!
        var window = UIApplication.shared.keyWindow
        if window?.windowLevel != UIWindowLevelNormal {
            let windows:Array = UIApplication.shared.windows
            for tmpWin:UIWindow in windows {
                if tmpWin.windowLevel == UIWindowLevelNormal {
                    window = tmpWin
                    break
                }
            }
        }
        
        let frontView = window?.subviews[0]
        let nextResponder = frontView?.next
        if (nextResponder?.isKind(of: UIViewController.self))! {
            result = nextResponder as? UIViewController
        } else {
            result = window?.rootViewController
        }
        return result
    }
    
    //MARK:----------全屏按鈕點擊事件
    @objc func onClickBackBtnAction(){
        //設(shè)置豎屏
        kAppdelegate?.blockRotation = .portrait
    }
    
    //MARK:----------暫停播放按鈕點擊方法
    @objc func playAndPause(btn:UIButton){
    let tmp = !playing
    playing = tmp // 改變狀態(tài)
    
    // 根據(jù)狀態(tài)設(shè)定圖片
    if playing {
        playBtn.setImage(UIImage(named: "pause"), for: .normal)
        player.play()
    }else{
        playBtn.setImage(UIImage(named: "play"), for: .normal)
        player.pause()
    }
   
    }
    
    //MARK:----------slider滑動方法
    @objc func sliderTouchDown(slider:UISlider){
        
        self.sliding = true
    }
    
    @objc func sliderTouchUpOut(slider:UISlider){
        
        if player.status == .readyToPlay {
            
            let duration = slider.value * Float(CMTimeGetSeconds(player.currentItem!.duration))
            let seekTime = CMTimeMake(Int64(duration), 1)
            
            player.seek(to: seekTime) { (bool) in
                
                self.sliding = false
            }
        }
        
    }

    //MARK:----------定時器方法
   @objc func update () {
    
    if playing == false {
        return
    }
    
    // 當(dāng)前播放到的時間
    let currentTime = CMTimeGetSeconds(player.currentTime())
    // 總時間
    let totalTime = TimeInterval(playerItem.duration.value)/TimeInterval(playerItem.duration.timescale)
    
    let timeStr = "\(formatPlayTime(seconds: currentTime))/\(formatPlayTime(seconds: totalTime))"
    timeLabel.text = timeStr
    if sliding == false {
        
        slider.value = Float(currentTime/totalTime)
        
    }
    }
    
    func formatPlayTime(seconds:TimeInterval)->String{
        
        if seconds.isNaN{
            return "00:00"
        }
        let Min:Int = Int(seconds / 60)
        let Sec:Int = Int(seconds) % 60
        return String(format: "%02d:%02d", Min, Sec)
    }    
}

上面我們用到了橫豎屏切換的kAppdelegate?.blockRotation為在Appdelegate寫的一個extension

let kAppdelegate: AppDelegate? = UIApplication.shared.delegate as? AppDelegate

extension AppDelegate{
    
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        
        return blockRotation
    }
}

 var blockRotation: UIInterfaceOrientationMask = .portrait{
        didSet{
            if blockRotation.contains(.portrait){
                //強制設(shè)置成豎屏
                UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
            }else{
                //強制設(shè)置成橫屏
                UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
                
            }
        }
    }

至此一個簡單的播放器就完成了单旁,包含播放暫停、快進快退饥伊、進度條象浑、緩沖條、視頻時間撵渡、橫豎屏切換融柬、左滑改變亮度、右滑改變聲音等等功能趋距。(改變亮度和聲音的功能只能在真機測試有效)

調(diào)用的時候粒氧,直接在viewDidLoad中:

 let playerView = CAplayerView(frame: CGRect(x: 0, y: 0, width: kScreenWidth, height: 250), theUrl: URL(string: "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-tpl-cc-us-20170912_1280x720h.mp4")!)
        playerView.backgroundColor = UIColor.black
        self.view.addSubview(playerView)

這樣即可。

當(dāng)然還有倍速播放节腐、清晰度切換外盯、緩存下載等等功能需要完善。實際應(yīng)用的時候還有對象釋放銷毀等問題翼雀。
因此此代碼僅供參考饱苟,如有問題可以回復(fù)跟我交流~

github地址:

https://github.com/WisdomWang/CAPlayer

最后編輯于
?著作權(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é)果婚禮上,老公的妹妹穿的比我還像新娘誊锭。我一直安慰自己表悬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布丧靡。 她就那樣靜靜地躺著蟆沫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪温治。 梳的紋絲不亂的頭發(fā)上饭庞,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音熬荆,去河邊找鬼舟山。 笑死,一個胖子當(dāng)著我的面吹牛卤恳,可吹牛的內(nèi)容都是我干的累盗。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼突琳,長吁一口氣:“原來是場噩夢啊……” “哼若债!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拆融,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤蠢琳,失蹤者是張志新(化名)和其女友劉穎啊终,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挪凑,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡孕索,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了躏碳。 大學(xué)時的朋友給我發(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
  • 正文 我出身青樓阁猜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹋艺。 傳聞我的和親對象是個殘疾皇子剃袍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

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