//
// C o p y r i g h t ( c ) 2 0 1 9 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
import Foundation
import MediaPlayer
import YYImage
import NVActivityIndicatorView
import SessionUIKit
public protocol MediaMessageViewAudioDelegate : AnyObject {
func progressChanged ( _ progressSeconds : CGFloat , durationSeconds : CGFloat )
}
public class MediaMessageView : UIView , OWSAudioPlayerDelegate {
public enum Mode : UInt {
case large
case small
case attachmentApproval
}
// MARK: P r o p e r t i e s
public let mode : Mode
public let attachment : SignalAttachment
public lazy var audioPlayer : OWSAudioPlayer ? = {
guard let dataUrl = attachment . dataUrl else { return nil }
return OWSAudioPlayer ( mediaUrl : dataUrl , audioBehavior : . playback , delegate : self )
} ( )
public var wasPlayingAudio : Bool = false
public var audioProgressSeconds : CGFloat = 0
public var audioDurationSeconds : CGFloat = 0
public weak var audioDelegate : MediaMessageViewAudioDelegate ?
public var playbackState = AudioPlaybackState . stopped {
didSet {
AssertIsOnMainThread ( )
ensureButtonState ( )
}
}
private lazy var validImage : UIImage ? = {
if attachment . isImage {
guard
attachment . isValidImage ,
let image : UIImage = attachment . image ( ) ,
image . size . width > 0 ,
image . size . height > 0
else {
return nil
}
return image
}
else if attachment . isVideo {
guard
attachment . isValidVideo ,
let image : UIImage = attachment . videoPreview ( ) ,
image . size . width > 0 ,
image . size . height > 0
else {
return nil
}
return image
}
return nil
} ( )
private lazy var validAnimatedImage : YYImage ? = {
guard
attachment . isAnimatedImage ,
attachment . isValidImage ,
let dataUrl : URL = attachment . dataUrl ,
let image : YYImage = YYImage ( contentsOfFile : dataUrl . path ) ,
image . size . width > 0 ,
image . size . height > 0
else {
return nil
}
return image
} ( )
private var linkPreviewInfo : ( url : String , draft : OWSLinkPreviewDraft ? ) ?
// MARK: I n i t i a l i z e r s
@ available ( * , unavailable , message : " use other constructor instead. " )
required public init ? ( coder aDecoder : NSCoder ) {
notImplemented ( )
}
// C u r r e n t l y w e o n l y u s e o n e m o d e ( A t t a c h m e n t A p p r o v a l ) , s o w e c o u l d s i m p l i f y t h i s c l a s s , b u t i t ' s k i n d
// o f n i c e t h a t i t ' s w r i t t e n i n a f l e x i b l e w a y i n c a s e w e ' d w a n t t o u s e i t e l s e w h e r e a g a i n i n t h e f u t u r e .
public required init ( attachment : SignalAttachment , mode : MediaMessageView . Mode ) {
if attachment . hasError { owsFailDebug ( attachment . error . debugDescription ) }
self . attachment = attachment
self . mode = mode
// S e t t h e l i n k P r e v i e w U r l i f i t ' s a u r l
if attachment . isUrl , let linkPreviewURL : String = OWSLinkPreview . previewURL ( forRawBodyText : attachment . text ( ) ) {
self . linkPreviewInfo = ( url : linkPreviewURL , draft : nil )
}
super . init ( frame : CGRect . zero )
setupViews ( )
setupLayout ( )
}
deinit {
NotificationCenter . default . removeObserver ( self )
}
// MARK: - U I
private lazy var stackView : UIStackView = {
let stackView : UIStackView = UIStackView ( )
stackView . translatesAutoresizingMaskIntoConstraints = false
stackView . axis = . vertical
stackView . alignment = . center
stackView . distribution = . fill
switch mode {
case . attachmentApproval : stackView . spacing = 2
case . large : stackView . spacing = 10
case . small : stackView . spacing = 5
}
return stackView
} ( )
private lazy var loadingView : NVActivityIndicatorView = {
let view : NVActivityIndicatorView = NVActivityIndicatorView ( frame : CGRect . zero , type : . circleStrokeSpin , color : Colors . text , padding : nil )
view . translatesAutoresizingMaskIntoConstraints = false
view . isHidden = true
return view
} ( )
private lazy var imageView : UIImageView = {
let view : UIImageView = UIImageView ( )
view . translatesAutoresizingMaskIntoConstraints = false
view . contentMode = . scaleAspectFit
view . image = UIImage ( named : " FileLarge " ) ? . withRenderingMode ( . alwaysTemplate )
view . tintColor = Colors . text
view . isHidden = true
// O v e r r i d e t h e i m a g e t o t h e c o r r e c t o n e
if attachment . isImage || attachment . isVideo {
if let validImage : UIImage = validImage {
view . layer . minificationFilter = . trilinear
view . layer . magnificationFilter = . trilinear
view . image = validImage
}
}
else if attachment . isUrl {
view . clipsToBounds = true
view . image = UIImage ( named : " Link " ) ? . withTint ( Colors . text )
view . contentMode = . center
view . backgroundColor = ( isDarkMode ? . black : UIColor . black . withAlphaComponent ( 0.06 ) )
view . layer . cornerRadius = 8
}
return view
} ( )
private lazy var fileTypeImageView : UIImageView = {
let view : UIImageView = UIImageView ( )
view . translatesAutoresizingMaskIntoConstraints = false
view . isHidden = true
return view
} ( )
private lazy var animatedImageView : YYAnimatedImageView = {
let view : YYAnimatedImageView = YYAnimatedImageView ( )
view . translatesAutoresizingMaskIntoConstraints = false
view . isHidden = true
if let image : YYImage = validAnimatedImage {
view . image = image
}
else {
view . contentMode = . scaleAspectFit
view . image = UIImage ( named : " FileLarge " ) ? . withRenderingMode ( . alwaysTemplate )
view . tintColor = Colors . text
}
return view
} ( )
lazy var videoPlayButton : UIImageView = {
let view : UIImageView = UIImageView ( image : UIImage ( named : " CirclePlay " ) )
view . translatesAutoresizingMaskIntoConstraints = false
view . contentMode = . scaleAspectFit
view . isHidden = true
return view
} ( )
// / N o t e : T h i s u s e s d i f f e r e n t a s s e t s f r o m t h e ` v i d e o P l a y B u t t o n ` a n d h a s a ' P a u s e ' s t a t e
private lazy var audioPlayPauseButton : UIButton = {
let button : UIButton = UIButton ( )
button . translatesAutoresizingMaskIntoConstraints = false
button . clipsToBounds = true
button . setBackgroundImage ( UIColor . white . toImage ( ) , for : . normal )
button . setBackgroundImage ( UIColor . white . darken ( by : 0.2 ) . toImage ( ) , for : . highlighted )
button . addTarget ( self , action : #selector ( audioPlayPauseButtonPressed ) , for : . touchUpInside )
button . isHidden = true
return button
} ( )
private lazy var titleStackView : UIStackView = {
let stackView : UIStackView = UIStackView ( )
stackView . translatesAutoresizingMaskIntoConstraints = false
stackView . axis = . vertical
stackView . alignment = ( attachment . isUrl && linkPreviewInfo ? . url != nil ? . leading : . center )
stackView . distribution = . fill
switch mode {
case . attachmentApproval : stackView . spacing = 2
case . large : stackView . spacing = 10
case . small : stackView . spacing = 5
}
return stackView
} ( )
private lazy var titleLabel : UILabel = {
let label : UILabel = UILabel ( )
label . translatesAutoresizingMaskIntoConstraints = false
// S t y l i n g
switch mode {
case . attachmentApproval :
label . font = UIFont . ows_boldFont ( withSize : ScaleFromIPhone5To7Plus ( 16 , 22 ) )
label . textColor = Colors . text
case . large :
label . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 18 , 24 ) )
label . textColor = Colors . accent
case . small :
label . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 14 , 14 ) )
label . textColor = Colors . accent
}
// C o n t e n t
if attachment . isUrl {
// I f w e h a v e n o l i n k p r e v i e w i n f o a t t h i s p o i n t t h e n a s s u m e l i n k p r e v i e w s a r e d i s a b l e d
if let linkPreviewURL : String = linkPreviewInfo ? . url {
label . font = . boldSystemFont ( ofSize : Values . smallFontSize )
label . text = linkPreviewURL
label . textAlignment = . left
label . lineBreakMode = . byTruncatingTail
label . numberOfLines = 2
}
else {
label . text = " vc_share_link_previews_disabled_title " . localized ( )
}
}
// T i t l e f o r e v e r y t h i n g e x c e p t t h e s e t y p e s
else if ! attachment . isImage && ! attachment . isAnimatedImage && ! attachment . isVideo {
if let fileName : String = attachment . sourceFilename ? . trimmingCharacters ( in : . whitespacesAndNewlines ) , fileName . count > 0 {
label . text = fileName
}
else if let fileExtension : String = attachment . fileExtension {
label . text = String (
format : " ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT " . localized ( ) ,
fileExtension . uppercased ( )
)
}
label . textAlignment = . center
label . lineBreakMode = . byTruncatingMiddle
}
// H i d e t h e l a b e l i f i t h a s n o c o n t e n t
label . isHidden = ( ( label . text ? . count ? ? 0 ) = = 0 )
return label
} ( )
private lazy var subtitleLabel : UILabel = {
let label : UILabel = UILabel ( )
label . translatesAutoresizingMaskIntoConstraints = false
// S t y l i n g
switch mode {
case . attachmentApproval :
label . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 12 , 18 ) )
label . textColor = Colors . pinIcon
case . large :
label . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 18 , 24 ) )
label . textColor = Colors . accent
case . small :
label . font = UIFont . ows_regularFont ( withSize : ScaleFromIPhone5To7Plus ( 14 , 14 ) )
label . textColor = Colors . accent
}
// C o n t e n t
if attachment . isUrl {
// W e o n l y l o a d L i n k P r e v i e w s f o r H T T P S u r l s s o a p p e n d a n e x p l a n a t i o n f o r n o t
if let linkPreviewURL : String = linkPreviewInfo ? . url {
if let targetUrl : URL = URL ( string : linkPreviewURL ) , targetUrl . scheme ? . lowercased ( ) != " https " {
label . font = UIFont . ows_regularFont ( withSize : Values . verySmallFontSize )
label . text = " vc_share_link_previews_unsecure " . localized ( )
label . textColor = ( mode = = . attachmentApproval ? Colors . pinIcon : Colors . accent )
}
}
// I f w e h a v e n o l i n k p r e v i e w i n f o a t t h i s p o i n t t h e n a s s u m e l i n k p r e v i e w s a r e d i s a b l e d
else {
label . text = " vc_share_link_previews_disabled_explanation " . localized ( )
label . textColor = Colors . text
label . textAlignment = . center
label . numberOfLines = 0
}
}
// S u b t i t l e f o r e v e r y t h i n g e l s e e x c e p t t h e s e t y p e s
else if ! attachment . isImage && ! attachment . isAnimatedImage && ! attachment . isVideo {
// F o r m a t s t r i n g f o r f i l e s i z e l a b e l i n c a l l i n t e r s t i t i a l v i e w .
// E m b e d s : { { f i l e s i z e a s ' N m b ' o r ' N k b ' } } .
let fileSize : UInt = attachment . dataLength
label . text = String ( format : " ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT " . localized ( ) , OWSFormat . formatFileSize ( UInt ( fileSize ) ) )
label . textAlignment = . center
}
// H i d e t h e l a b e l i f i t h a s n o c o n t e n t
label . isHidden = ( ( label . text ? . count ? ? 0 ) = = 0 )
return label
} ( )
// MARK: - L a y o u t
private func setupViews ( ) {
// P l a i n t e x t w i l l j u s t b e p u t i n t h e ' m e s s a g e ' i n p u t s o d o n o t h i n g
guard ! attachment . isText && ! attachment . isOversizeText else { return }
// S e t u p t h e v i e w h i e r a r c h y
addSubview ( stackView )
addSubview ( loadingView )
addSubview ( videoPlayButton )
stackView . addArrangedSubview ( imageView )
stackView . addArrangedSubview ( animatedImageView )
if ! titleLabel . isHidden { stackView . addArrangedSubview ( UIView . vhSpacer ( 10 , 10 ) ) }
stackView . addArrangedSubview ( titleStackView )
titleStackView . addArrangedSubview ( titleLabel )
titleStackView . addArrangedSubview ( subtitleLabel )
imageView . addSubview ( fileTypeImageView )
// T y p e - s p e c i f i c c o n f i g u r a t i o n s
if attachment . isAnimatedImage {
animatedImageView . isHidden = false
}
else if attachment . isImage {
imageView . isHidden = false
}
else if attachment . isVideo {
// N o t e : T h e ' a t t a c h m e n t A p p r o v a l ' m o d e p r o v i d e s i t ' s o w n p l a y b u t t o n t o k e e p
// i t a t t h e p r o p e r s c a l e w h e n z o o m i n g
imageView . isHidden = false
videoPlayButton . isHidden = ( mode = = . attachmentApproval )
}
else if attachment . isAudio {
// H i d e t h e ' a u d i o P l a y P a u s e B u t t o n ' i f t h e ' a u d i o P l a y e r ' f a i l e d t o g e t c r e a t e d
imageView . isHidden = false
audioPlayPauseButton . isHidden = ( audioPlayer = = nil )
setAudioIconToPlay ( )
setAudioProgress ( 0 , duration : ( audioPlayer ? . duration ? ? 0 ) )
fileTypeImageView . image = UIImage ( named : " table_ic_notification_sound " ) ?
. withRenderingMode ( . alwaysTemplate )
fileTypeImageView . tintColor = Colors . text
fileTypeImageView . isHidden = false
// N o t e : T h e r e i s a n a n n o y i n g b u g w h e r e t h e M e d i a M e s s a g e V i e w w i l l f i l l t h e s c r e e n i f t h e
// ' a u d i o P l a y P a u s e B u t t o n ' i s a d d e d a n y w h e r e w i t h i n t h e v i e w h i e r a r c h y c a u s i n g i s s u e s w i t h
// t h e m i n s c a l e o n ' i m a g e ' a n d ' a n i m a t e d I m a g e ' f i l e t y p e s ( a s s u m e i t ' s a c t u a l l y a n y U I B u t t o n )
addSubview ( audioPlayPauseButton )
}
else if attachment . isUrl {
imageView . isHidden = false
imageView . alpha = 0 // N o t ' i s H i d d e n ' b e c a u s e w e w a n t i t t o t a k e u p s p a c e i n t h e U I S t a c k V i e w
loadingView . isHidden = false
if let linkPreviewUrl : String = linkPreviewInfo ? . url {
// D o n ' t w a n t t o c h a n g e t h e a x i s u n t i l w e h a v e a U R L t o s t a r t l o a d i n g , o t h e r w i s e t h e
// e r r o r m e s s a g e w i l l b e b r o k e n
stackView . axis = . horizontal
loadLinkPreview ( linkPreviewURL : linkPreviewUrl )
}
}
else {
imageView . isHidden = false
}
}
private func setupLayout ( ) {
// P l a i n t e x t w i l l j u s t b e p u t i n t h e ' m e s s a g e ' i n p u t s o d o n o t h i n g
guard ! attachment . isText && ! attachment . isOversizeText else { return }
// S i z i n g c a l c u l a t i o n s
let clampedRatio : CGFloat = {
if attachment . isUrl {
return 1
}
if attachment . isAnimatedImage {
let imageSize : CGSize = ( animatedImageView . image ? . size ? ? CGSize ( width : 1 , height : 1 ) )
let aspectRatio : CGFloat = ( imageSize . width / imageSize . height )
return CGFloatClamp ( aspectRatio , 0.05 , 95.0 )
}
// A l l o t h e r t y p e s s h o u l d m a i n t a i n t h e r a t i o o f t h e i m a g e i n t h e ' i m a g e V i e w '
let imageSize : CGSize = ( imageView . image ? . size ? ? CGSize ( width : 1 , height : 1 ) )
let aspectRatio : CGFloat = ( imageSize . width / imageSize . height )
return CGFloatClamp ( aspectRatio , 0.05 , 95.0 )
} ( )
let maybeImageSize : CGFloat ? = {
if attachment . isImage || attachment . isVideo {
if validImage != nil { return nil }
// I f w e d o n ' t h a v e a v a l i d i m a g e t h e n u s e t h e ' g e n e r i c ' c a s e
}
else if attachment . isAnimatedImage {
if validAnimatedImage != nil { return nil }
// I f w e d o n ' t h a v e a v a l i d i m a g e t h e n u s e t h e ' g e n e r i c ' c a s e
}
else if attachment . isUrl {
return 80
}
// G e n e r i c f i l e s i z e
switch mode {
case . large : return 200
case . attachmentApproval : return 120
case . small : return 80
}
} ( )
let imageSize : CGFloat = ( maybeImageSize ? ? 0 )
let audioButtonSize : CGFloat = ( imageSize / 2.5 )
audioPlayPauseButton . layer . cornerRadius = ( audioButtonSize / 2 )
// A c t u a l l a y o u t
NSLayoutConstraint . activate ( [
stackView . centerXAnchor . constraint ( equalTo : centerXAnchor ) ,
stackView . centerYAnchor . constraint ( equalTo : centerYAnchor ) ,
stackView . heightAnchor . constraint ( lessThanOrEqualTo : heightAnchor ) ,
( maybeImageSize != nil ?
stackView . widthAnchor . constraint (
equalTo : widthAnchor ,
constant : ( attachment . isUrl ? - ( 32 * 2 ) : 0 ) // I n s e t s t a c k V i e w f o r u r l s
) :
stackView . widthAnchor . constraint ( lessThanOrEqualTo : widthAnchor )
) ,
imageView . widthAnchor . constraint (
equalTo : imageView . heightAnchor ,
multiplier : clampedRatio
) ,
animatedImageView . widthAnchor . constraint (
equalTo : animatedImageView . heightAnchor ,
multiplier : clampedRatio
) ,
// N o t e : A n i m a t e d I m a g e , I m a g e a n d V i d e o t y p e s s h o u l d a l l o w z o o m i n g s o b e l e s s T h a n O r E q u a l T o
// t h e v i e w s i z e b u t s o m e o t h e r t y p e s s h o u l d h a v e s p e c i f i c s i z e s
animatedImageView . widthAnchor . constraint ( lessThanOrEqualTo : widthAnchor ) ,
animatedImageView . heightAnchor . constraint ( lessThanOrEqualTo : heightAnchor ) ,
( maybeImageSize != nil ?
imageView . widthAnchor . constraint ( equalToConstant : imageSize ) :
imageView . widthAnchor . constraint ( lessThanOrEqualTo : widthAnchor )
) ,
( maybeImageSize != nil ?
imageView . heightAnchor . constraint ( equalToConstant : imageSize ) :
imageView . heightAnchor . constraint ( lessThanOrEqualTo : heightAnchor )
) ,
fileTypeImageView . centerXAnchor . constraint ( equalTo : imageView . centerXAnchor ) ,
fileTypeImageView . centerYAnchor . constraint (
equalTo : imageView . centerYAnchor ,
constant : ceil ( imageSize * 0.15 )
) ,
fileTypeImageView . widthAnchor . constraint (
equalTo : fileTypeImageView . heightAnchor ,
multiplier : ( ( fileTypeImageView . image ? . size . width ? ? 1 ) / ( fileTypeImageView . image ? . size . height ? ? 1 ) )
) ,
fileTypeImageView . widthAnchor . constraint ( equalTo : imageView . widthAnchor , multiplier : 0.5 ) ,
videoPlayButton . centerXAnchor . constraint ( equalTo : centerXAnchor ) ,
videoPlayButton . centerYAnchor . constraint ( equalTo : centerYAnchor ) ,
loadingView . centerXAnchor . constraint ( equalTo : imageView . centerXAnchor ) ,
loadingView . centerYAnchor . constraint ( equalTo : imageView . centerYAnchor ) ,
loadingView . widthAnchor . constraint ( equalToConstant : ceil ( imageSize / 3 ) ) ,
loadingView . heightAnchor . constraint ( equalToConstant : ceil ( imageSize / 3 ) )
] )
// N o i n s e t f o r t h e t e x t f o r U R L s b u t t h e r e i s f o r a l l o t h e r l a y o u t s
if ! attachment . isUrl {
NSLayoutConstraint . activate ( [
titleLabel . widthAnchor . constraint ( equalTo : stackView . widthAnchor , constant : - ( 32 * 2 ) ) ,
subtitleLabel . widthAnchor . constraint ( equalTo : stackView . widthAnchor , constant : - ( 32 * 2 ) )
] )
}
// N o t e : T h e r e i s a n a n n o y i n g b u g w h e r e t h e M e d i a M e s s a g e V i e w w i l l f i l l t h e s c r e e n i f t h e
// ' a u d i o P l a y P a u s e B u t t o n ' i s a d d e d a n y w h e r e w i t h i n t h e v i e w h i e r a r c h y c a u s i n g i s s u e s w i t h
// t h e m i n s c a l e o n ' i m a g e ' a n d ' a n i m a t e d I m a g e ' f i l e t y p e s ( a s s u m e i t ' s a c t u a l l y a n y U I B u t t o n )
if attachment . isAudio {
NSLayoutConstraint . activate ( [
audioPlayPauseButton . centerXAnchor . constraint ( equalTo : imageView . centerXAnchor ) ,
audioPlayPauseButton . centerYAnchor . constraint ( equalTo : imageView . centerYAnchor ) ,
audioPlayPauseButton . widthAnchor . constraint ( equalToConstant : audioButtonSize ) ,
audioPlayPauseButton . heightAnchor . constraint ( equalToConstant : audioButtonSize ) ,
] )
}
}
// MARK: - L i n k L o a d i n g
private func loadLinkPreview ( linkPreviewURL : String ) {
loadingView . startAnimating ( )
OWSLinkPreview . tryToBuildPreviewInfo ( previewUrl : linkPreviewURL )
. done { [ weak self ] draft in
// TODO: L o o k a t r e f a c t o r i n g t h i s b e h a v i o u r t o c o n s o l i d a t e a t t a c h m e n t m u t a t i o n s
self ? . attachment . linkPreviewDraft = draft
self ? . linkPreviewInfo = ( url : linkPreviewURL , draft : draft )
// U p d a t e t h e U I
self ? . titleLabel . text = ( draft . title ? ? self ? . titleLabel . text )
self ? . loadingView . alpha = 0
self ? . loadingView . stopAnimating ( )
self ? . imageView . alpha = 1
if let jpegImageData : Data = draft . jpegImageData , let loadedImage : UIImage = UIImage ( data : jpegImageData ) {
self ? . imageView . image = loadedImage
self ? . imageView . contentMode = . scaleAspectFill
}
}
. catch { [ weak self ] _ in
self ? . loadingView . alpha = 0
self ? . loadingView . stopAnimating ( )
self ? . imageView . alpha = 1
self ? . titleLabel . numberOfLines = 1 // T r u n c a t e s t h e U R L a t 1 l i n e s o t h e e r r o r i s m o r e r e a d a b l e
self ? . subtitleLabel . isHidden = false
// S e t t h e e r r o r t e x t a p p r o p r i a t e l y
if let targetUrl : URL = URL ( string : linkPreviewURL ) , targetUrl . scheme ? . lowercased ( ) != " https " {
// T h i s e r r o r c a s e i s h a n d l e d a l r e a d y i n t h e ' s u b t i t l e L a b e l ' c r e a t i o n
}
else {
self ? . subtitleLabel . font = UIFont . ows_regularFont ( withSize : Values . verySmallFontSize )
self ? . subtitleLabel . text = " vc_share_link_previews_error " . localized ( )
self ? . subtitleLabel . textColor = ( self ? . mode = = . attachmentApproval ? Colors . pinIcon : Colors . accent )
self ? . subtitleLabel . textAlignment = . left
}
}
. retainUntilComplete ( )
}
// MARK: - F u n c t i o n s
public func playAudio ( ) {
audioPlayer ? . play ( )
ensureButtonState ( )
}
public func pauseAudio ( ) {
wasPlayingAudio = ( audioPlayer ? . isPlaying = = true )
// I f t h e ' a u d i o P l a y e r ' h a s a d u r a t i o n o f 0 t h e n w e p r o b a b l y h a v e n ' t p l a y e d p r e v i o u s l y w h i c h
// w i l l r e s u l t i n t h e a u d i o P l a y e r h a v i n g a ' d u r a t i o n ' o f 0 b r e a k i n g t h e p r o g r e s s B a r . W e p l a y
// t h e a u d i o t o g e t i t t o p r o p e r l y l o a d t h e f i l e r i g h t b e f o r e p a u s i n g i t s o t h e d a t a i s
// l o a d e d c o r r e c t l y
if audioPlayer ? . duration = = 0 {
audioPlayer ? . play ( )
}
audioPlayer ? . pause ( )
ensureButtonState ( )
}
public func setAudioTime ( currentTime : TimeInterval ) {
audioPlayer ? . setCurrentTime ( currentTime )
}
// MARK: - E v e n t H a n d l e r s
@objc func audioPlayPauseButtonPressed ( sender : UIButton ) {
audioPlayer ? . togglePlayState ( )
}
// MARK: - O W S A u d i o P l a y e r D e l e g a t e
public func audioPlaybackState ( ) -> AudioPlaybackState {
return playbackState
}
public func setAudioPlaybackState ( _ value : AudioPlaybackState ) {
playbackState = value
}
public func showInvalidAudioFileAlert ( ) {
OWSAlerts . showErrorAlert ( message : NSLocalizedString ( " INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE " , comment : " Message for the alert indicating that an audio file is invalid. " ) )
}
public func audioPlayerDidFinishPlaying ( _ player : OWSAudioPlayer , successfully flag : Bool ) {
// D o n o t h i n g
}
private func ensureButtonState ( ) {
switch playbackState {
case . playing : setAudioIconToPause ( )
default : setAudioIconToPlay ( )
}
}
public func setAudioProgress ( _ progress : CGFloat , duration : CGFloat ) {
// N o t e : W h e n t h e O W S A u d i o P l a y e r s t o p s i t s e t s t h e d u r a t i o n t o 0 ( w h i c h w e w a n t t o i g n o r e s o
// t h e U I d o e s n ' t l o o k b u g g y )
let finalDuration : CGFloat = ( duration > 0 ? duration : audioDurationSeconds )
audioProgressSeconds = progress
audioDurationSeconds = finalDuration
audioDelegate ? . progressChanged ( progress , durationSeconds : finalDuration )
}
private func setAudioIconToPlay ( ) {
audioPlayPauseButton . setImage ( UIImage ( named : " Play " ) , for : . normal )
}
private func setAudioIconToPause ( ) {
audioPlayPauseButton . setImage ( UIImage ( named : " Pause " ) , for : . normal )
}
}