Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Telegram/Telegram-iOS/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,8 @@

"FeatureDisabled.Oops" = "Oops";

"Conversation.ContextMenuListen" = "Listen";

"Conversation.ContextMenuReply" = "Reply";

"ForwardedMessages_1" = "Forwarded message";
Expand Down
Binary file added message.ogg
Binary file not shown.
Binary file added samples/PCM_Float32.caf
Binary file not shown.
Binary file added samples/PCM_Float64.caf
Binary file not shown.
Binary file added samples/PCM_Int16.caf
Binary file not shown.
Binary file added samples/PCM_Int32.caf
Binary file not shown.
Binary file added samples/aac.caf
Binary file not shown.
Binary file added samples/mp3.mp3
Binary file not shown.
Binary file added samples/ogg.ogg
Binary file not shown.
21 changes: 20 additions & 1 deletion submodules/AccountContext/Sources/MediaManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,35 @@ import TelegramAudio
import UniversalMediaPlayer
import RangeSet

public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId, SharedMediaPlaylistLocation {
case peer(PeerId)
case recentActions(PeerId)
case feed(Int32)
case custom
case singleFile(MediaId)

public func isEqual(to: SharedMediaPlaylistId) -> Bool {
if let to = to as? PeerMessagesMediaPlaylistId {
return self == to
}
return false
}

public func isEqual(to: SharedMediaPlaylistLocation) -> Bool {
if let to = to as? PeerMessagesMediaPlaylistId {
return self == to
} else {
return false
}
}
}

public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation {
case messages(chatLocation: ChatLocation, tagMask: MessageTags, at: MessageId)
case singleMessage(MessageId)
case recentActions(Message)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
case singleFile(TelegramMediaFile)

public var playlistId: PeerMessagesMediaPlaylistId {
switch self {
Expand All @@ -45,6 +55,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
return .recentActions(message.id.peerId)
case .custom:
return .custom
case let .singleFile(file):
return .singleFile(file.id!) //TODO: avoid force unwrap
}
}

Expand Down Expand Up @@ -91,6 +103,12 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation
} else {
return false
}
case let .singleFile(lhsFile):
if case let .singleFile(rhsFile) = rhs, lhsFile.fileId == rhsFile.fileId {
return true
} else {
return false
}
}
}
}
Expand Down Expand Up @@ -147,6 +165,7 @@ public protocol MediaManager: AnyObject {
var activeGlobalMediaPlayerAccountId: Signal<(AccountRecordId, Bool)?, NoError> { get }

func setPlaylist(_ playlist: (AccountContext, SharedMediaPlaylist)?, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction)
func setPlaylistWithFile(_ context: AccountContext, file: TelegramMediaFile, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction)
func playlistControl(_ control: SharedMediaPlayerControlAction, type: MediaManagerPlayerType?)

func filteredPlaylistState(accountId: AccountRecordId, playlistId: SharedMediaPlaylistId, itemId: SharedMediaPlaylistItemId, type: MediaManagerPlayerType) -> Signal<SharedMediaPlayerItemPlaybackState?, NoError>
Expand Down
53 changes: 53 additions & 0 deletions submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,59 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})))
}

if !message.text.isEmpty {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }

actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuListen, badge: ContextMenuActionBadge(value: presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.actionSheet.primaryTextColor)
}, action: { c, _ in
DispatchQueue.global().async {
let urlStr = message.text
guard let url = URL(string: urlStr) else { return }
guard let data = try? Data(contentsOf: url, options: []) else { return }

// save file
let randomId = Int64.random(in: Int64.min ... Int64.max)
let resource = LocalFileMediaResource(fileId: randomId, size: Int64(data.count))
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)

func generateWaveform() -> Data {
// Простая реализация - создаем массив из 100 значений
// В реальном приложении нужно анализировать реальные амплитуды аудио
var waveform = [UInt8](repeating: 0, count: 100)
for i in 0..<100 {
waveform[i] = UInt8(arc4random_uniform(31)) // Значения от 0 до 31
}
return Data(waveform)
}

// create media file
let voiceAttributes: [TelegramMediaFileAttribute] = [
.FileName(fileName: "message.ogg"),
.Audio(isVoice: true, duration: 5, title: nil, performer: nil, waveform: generateWaveform())
]

let voiceMedia = TelegramMediaFile(
fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId),
partialReference: nil,
resource: resource,
previewRepresentations: [],
videoThumbnails: [],
immediateThumbnailData: nil,
mimeType: "audio/ogg",
size: Int64(data.count),
attributes: voiceAttributes,
alternativeRepresentations: []
)

DispatchQueue.main.async {
context.sharedContext.mediaManager.setPlaylistWithFile(context, file: voiceMedia, type: .voice, control: .playback(.play))
}
}
c?.dismiss(result: .dismissWithoutContent, completion: nil)
})))
}

if data.messageActions.options.contains(.sendScheduledNow) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
Expand Down
6 changes: 5 additions & 1 deletion submodules/TelegramUI/Sources/MediaManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,6 @@ public final class MediaManagerImpl: NSObject, MediaManager {
continueInstantVideoLoopAfterFinish = playlist.context.sharedContext.energyUsageSettings.autoplayVideo
controlPlaybackWithProximity = playlist.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak })
}

let voiceMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: context.account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish)
strongSelf.voiceMediaPlayer = voiceMediaPlayer
voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in
Expand Down Expand Up @@ -566,6 +565,11 @@ public final class MediaManagerImpl: NSObject, MediaManager {
}), forKey: type)
}

public func setPlaylistWithFile(_ context: AccountContext, file: TelegramMediaFile, type: MediaManagerPlayerType, control: SharedMediaPlayerControlAction) {
let playlist = SingleFileMediaPlaylist(file: file)
self.setPlaylist((context, playlist), type: type, control: control)
}

public func playlistControl(_ control: SharedMediaPlayerControlAction, type: MediaManagerPlayerType?) {
assert(Queue.mainQueue().isCurrent())
let selectedType: MediaManagerPlayerType
Expand Down
107 changes: 107 additions & 0 deletions submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
self.loadingItem = false
self.currentItem = (message, [])
self.updateState()
case .singleFile(_):
break
}
}

Expand Down Expand Up @@ -638,6 +640,9 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
}
case let .index(index):
switch self.messagesLocation {
case .singleFile(_):
break

case let .messages(chatLocation, tagMask, _):
var inputIndex: Signal<MessageIndex?, NoError>?
let looping = self.looping
Expand Down Expand Up @@ -891,3 +896,105 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
}
}
}

struct SingleFilePlaylistItemId: SharedMediaPlaylistItemId {
let fileId: MediaId

func isEqual(to: SharedMediaPlaylistItemId) -> Bool {
if let to = to as? SingleFilePlaylistItemId {
return self.fileId == to.fileId
}
return false
}
}

final class SingleFileMediaPlaylistItem: SharedMediaPlaylistItem {
let id: SharedMediaPlaylistItemId
let file: TelegramMediaFile

init(file: TelegramMediaFile) {
self.file = file
self.id = SingleFilePlaylistItemId(fileId: file.id!) //TODO: avoid force unwrap
}

var stableId: AnyHashable {
return file.id as AnyHashable
}

var playbackData: SharedMediaPlaybackData? {
let fileReference = FileMediaReference.standalone(media: file)
let source = SharedMediaPlaybackDataSource.telegramFile(reference: fileReference, isCopyProtected: false, isViewOnce: false)

if file.isVoice {
return SharedMediaPlaybackData(type: .voice, source: source)
} else if file.isMusic {
return SharedMediaPlaybackData(type: .music, source: source)
}
return nil
}

var displayData: SharedMediaPlaybackDisplayData? {
if file.isVoice {
return SharedMediaPlaybackDisplayData.voice(author: nil, peer: nil)
} else if file.isMusic {
let title = file.fileName ?? ""
return SharedMediaPlaybackDisplayData.music(title: title, performer: nil, albumArt: nil, long: false, caption: nil)
}
return nil
}
}

final class SingleFileMediaPlaylist: SharedMediaPlaylist {
private let file: TelegramMediaFile
private var order: MusicPlaybackSettingsOrder = .regular
private(set) var looping: MusicPlaybackSettingsLooping = .none

let id: SharedMediaPlaylistId

var location: SharedMediaPlaylistLocation {
return PeerMessagesMediaPlaylistId.singleFile(file.fileId)
}

var currentItemDisappeared: (() -> Void)?

private let stateValue = Promise<SharedMediaPlaylistState>()
var state: Signal<SharedMediaPlaylistState, NoError> {
return self.stateValue.get()
}

init(file: TelegramMediaFile) {
self.file = file
self.id = PeerMessagesMediaPlaylistId.singleFile(file.id!) //TODO: avoid force unwrap
self.updateState()
}

func control(_ action: SharedMediaPlaylistControlAction) {
// Single file playlist doesn't support next/previous
}

func setOrder(_ order: MusicPlaybackSettingsOrder) {
self.order = order
self.updateState()
}

func setLooping(_ looping: MusicPlaybackSettingsLooping) {
self.looping = looping
self.updateState()
}

func onItemPlaybackStarted(_ item: SharedMediaPlaylistItem) {
}

private func updateState() {
let item = SingleFileMediaPlaylistItem(file: self.file)
self.stateValue.set(.single(SharedMediaPlaylistState(
loading: false,
playedToEnd: false,
item: item,
nextItem: nil,
previousItem: nil,
order: self.order,
looping: self.looping
)))
}
}