Sekitar empat bulan yang lalu, saya membuat aplikasi kecil kecil-an bernama Cincau, aplikasi nya belum ku coba rilis di App Store, jadi ini aplikasi sederhana saja untuk mendownload video TikTok, dengan gaya ku sendiri.
Inspirasi ini, kebetulan saya mendapat referensi API untuk mendownload-nya, gateway nya dari China, menggunakan API dari website Tikwm, ini website memang memberikan fitur download dengan berbagai format watermark atau tidak menggunakan watermark, jadi aku coba ingin integrasikan di iOS.
Pertama yang ku lakuin adalah, mencoba API nya terlebih dahulu
let BASE_URL = "https://tikwm.com";
func translateForVideoData(_ url: String) async throws -> TiktokData {
let url = try makeURL(BASE_URL, path: "/api", videoUrl: url)
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(USER_AGENT, forHTTPHeaderField: "User-Agent")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
struct Response: Decodable {
let videoData: TiktokData
enum CodingKeys: String, CodingKey {
case videoData = "data"
}
}
let response: Response = try await decodedData(for: request)
return response.videoData
}
Dari fungsi di atas, kita sudah memasukkan Response dengan struktur TiktokData, sesuai dengan API yang ku coba untuk mendapatkan source link download.
Setelah itu aku coba menyesuaikan struktur data-nya dari respon JSON-nya, berikut sekilasnya dengan parameter Dari sekilas data Sebenarnya masih banyak variable data lainnya, jika kamu langsung melihat response JSON nya langsung.Respond Tiktok Video Data JSON
https://tikwm.com/api/?url=https://www.tiktok.com/@moviesaftermidnight/video/7314688804038642977&hd=1url, adalah link video tiktokTiktokData, menjelaskan sedikit, ada tiga URL di sini, salah satu-nya yang play itu video tanpa watermark dengan resolusi biasa dan `hdPlay' video tanpa watermark dengan resolusi tinggi.
struct TiktokData: Decodable {
let id: String
let title: String
let play: URL
let wmPlay: URL
let hdPlay: URL
enum CodingKeys: String, CodingKey {
case id
case title
case play
case wmPlay = "wmplay"
case hdPlay = "hdplay"
case size
}
Selain itu saya juga menambahkan integrasi API untuk menampilkan daftar video dari @username yang di-input. Di halaman Contoh link API-nya menggunakan parameter Ini salah satu bonus fitur yang ku terapkan juga di aplikasi Cincau, jika User mengetik tulisan @username nya di input utama-nya, akan tampil halaman daftar video profile user-nya, jadi tinggal memilih, sekalian langsung bisa didownload di dalamnya.ProfileView sendiri.List Video dari User Respond JSON
unique_id untuk mengisi username-nya.https://www.tikwm.com/api/user/posts?unique_id=@drzaidulakbar.official&hd=1struct ProfileFeed: Decodable {
let code: Int
let msg: String
let processed_time: Double
let datas: ListVideos
struct ListVideos: Decodable {
let videos: [ProfileVideo]
let cursor: String
let hasMore: Bool
enum CodingKeys: String, CodingKey {
case videos
case cursor
case hasMore
}
}
enum CodingKeys: String, CodingKey {
case code
case msg
case processed_time
case datas = "data"
}
}
Sebelum kita bisa memutar video tersebut langsung di aplikasinya
Kita harus menyelipkan AVPlayer di dalam UIViewControllerRepresentable, karena Video Player di SwiftUI mungkin belum support sejak aku mengembangkan aplikasi ini, jadi kita membuat Custom View sendiri di untuk SwiftUI-nyaContoh AVPlayerView untuk UIViewControllerRepresentable
struct AVPlayerView: UIViewControllerRepresentable {
let player: AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
AVPlayerViewController()
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
if uiViewController.player == nil {
uiViewController.player = player
uiViewController.showsPlaybackControls = true
// player.play()
}
}
static func dismantleUIViewController(_ uiViewController: AVPlayerViewController, coordinator: ()) {
uiViewController.player?.pause()
uiViewController.player = nil
}
}
Setelah itu, kita membuat View bernama ShortVideoPlayer untuk halaman sekilas tampilan video-nya, jadi di sini agak tricky selama mengembangkannya, apalagi soal posisi dan menampilkan properti data di atas background video-nya, juga tombol untuk Mute dan menghilangkan tulisan-nya.
struct ShortVideoPlayer: View {
var body: someView {
GeometryReader {
ZStack {
AVPlayerView(player: model.player).ignoresSafeArea().frame(width: geo.size.height * 16 / 9,
height: geo.size.height)
if model.is_loading {
ProgressView()
.progressViewStyle(.circular)
.scaleEffect(CGSize(width: 1.5, height: 1.5))
}
}
}
}
}Sekarang bagaimana ShortVideoPlayer tersebut mendapatkan URL link video-nya, pasti ada yang kurangkan dari sini, kita bisa menambah parameter Selanjutnya kita mempersiapkan operasional AVPlayerView juga belum tentu mendapatkan Player-nya, AVPlayerView hanya kosongan televisi-nya, tapi tukang mutarnya belum ada ni, jadi apa yang kita tambah di sini. dari hint-nya yang AVPlayerLanjutan ShortVideoPlayer
url dan juga StateObject model dari ShortVideoPlayerViewModel. Kayaknya aku pernah membahas basic SwiftUI seperti property wrapper di struktur SwiftUI.
struct ShortVideoPlayer: View {
let url: URL
@StateObject var model: ShortVideoPlayerViewModel
init(url: URL, video_size: Binding<CGSize?>) {
self.url = url;
_model = StateObject(wrappedValue, ShortVideoPlayerViewModel(url: url, video_size: video_size)
}
}
AVPlayer di ObservableObject, di sini yang mengaktifkan flow ShortVideoPlayer nya, dari video ini loading, hingga kita mute suara-nya.@MainActor
class ShortVideoPlayerViewModel: ObservableObject {
private let url: URL
private let player_item: AVPlayerItem
let player: AVPlayer
init(url: URL, video_size: Binding<CGSize?>) {
self.url = url
player_item = AVPlayerItem(url: url)
palyer = AVPlayer(playerItem: player_item)
}
}
Selanjutnya kita coba tampilkan di tampilan SwiftUI-nya, dengan berisi Input URL yang bisa menerima copy paste dari source lain.
Di dalam DetailVideoView, adalah preview Video-nya sebelum pengguna men-download videonya, di mana pengguna melihat deskripsi post video-nya, pilihan download-nya, bahkan tertera juga judul soundtracknya.
struct DetailVideoView: View {
@State private var tiktokUrl = ""
@StateObject private var converseViewModel: ConverseViewModel
var body: some View {
ZStack {
Color(.black).ignoresSafeArea()
ShortVideoPlayer(url: converseViewModel.playUrl!, video_size: .constant(nil),
controller: VideoControler(), tapInteraction: {
withAnimation {
show_texts.toggle()
}
})
}.sheet(isPresented: $show_share_sheet) {
if let videoData = con
}
}
Sekarang DetailVideoView-nya sudah beres, sekarang kita kerjakan bagaimana tombol Download itu bekerja,
Ini bagaimana kita mencoba mendownloadnya dengan menggunakan URLSession, dengan disampingkan state agar
progress Download tersebut berlangsung.
func downloadVideo(url: URL) {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: url) { (data, response, error) in
guard error == nil else {
return
}
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
}
task.resume()
withAnimation {
self.state = .conversing
downloading = true
}
}
URLSession menggunakan Delegate untuk mengambil progress partikel partikel file yang sedang didownload, jadi kita arahkan untuk mmenyimpan nya langsung di koleksi album video kita
Di dalam fungsi delegate didFinishDownloadingTo
FileManagersaveVideoToAlbum
extension ConverseViewModel: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let data = try? Data(contentsOf: location) else {
return
}
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationURL = documentsURL.appendingPathComponent("\(title).mp4")
do {
try data.write(to: destinationURL)
saveVideoToAlbum(videoURL: destinationURL, albumName: "Cincau")
withAnimation {
self.state = .conversed(destinationURL)
self.downloading = false
}
} catch {
withAnimation {
self.state = .error
self.downloading = false
}
print("Error saving file:", error)
}
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
totalWritten = downloadTask.progress.fractionCompleted
}
}
jadi setelah data sudah terbentuk selesai, kita menyimpan nya lewat Menyimpan Video langsung ke Album yang sudah eksissaveVideoToAlbum, di sini aku kasih hint saja di dalam fungsi tersebut.Save Video To Album
private func saveVideoToAlbum(videoURL: URL, albumName: String) {
if albumExists(albumName: albumName) {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", albumName)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOp tions)
if let album = collection.firstObject {
saveVideo(videoURL: videoURL, to: album)
}
}
}
Pertama kita pastikan album khusus untuk aplikasi yang ter-download sudah terbuat, jika sudah eksis kita bisa menggunakan PHFetchOptions
Jadi kita akan membuat fungsi private saveVideo, dengan menggunakan PHPhotoLibrary
private func saveVideo(videoURL: URL, to album: PHAssetCollection) {
PHPhotoLibrary.shared().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let enumeration: NSArray = [assetChangeRequest!.placeholderForCreatedAsset!]
albumChangeRequest?.addAssets(enumeration)
}, completionHandler: { success, error in
if success {
print("Successfully saved video to album")
} else {
print("Error saving video to album: \(error?.localizedDescription ?? "")")
}
})
}
Video TikTok sudah tersimpan di album yang sudah di siapkan di aplikasi Photos, secara garis besar begini proses bagaimana kita bisa memanfaatkan API tersebut untuk mendownload video TikTok di iOS.
Referensi:
Thanks for reads, hope you enjoyed it, sharing this article on your favorite social media network would be highly appreciated 💖! Sawernya juga boleh