Membuat aplikasi konverter youtube ke Mp3 mutiplatform

Mon, Apr 8, 2019 7-minute read

Sering sekali saya menemukan lagu yang menurut pendapat saya bagus di Youtube, akan tetapi saya hanya ingin mendengarkan lagunya saja tidak dengan videonya.

Alih-alih ingin menghemat kuota internet dirumah (Indihome berkuota 😂), saya sering melakukan konvert youtube ke mp3 dengan aplikasi web ini Online Video Converter kalo saya sedang memakai komputer…

Bagaimana kalo di smartphone?

Beberapa kali saya mencoba donwload aplikasi dari Apps Store ada dua jenis aplikasi, yaitu aplikasi gratis tetapi banyak iklan atau yang tidak ada iklan tetapi berbayar, hehe… 😅

Gimana kalo coba bikin aplikasi konverter youtube ke Mp3 sendiri?

Yaps, bisa donk. Semenjak bercinta dengan Flutter bikin aplikasi mobile multiplatform (Android/IOS) jadi mudah, karena everything is a widget !!!

Kita akan menggunakan tools Youtube Donwloader untuk mengambil data dan konversi ke format MP3.

# Persiapan

Seperti biasa, jalankan editor Visual Studio Code, Selanjutnya buat projek baru dengan menekan kombinasi tombol CTRL+SHIFT+P dan tulis nama projeknya misalnya konverter-youtube-mp3. Untuk nama aplikasi harus huruf kecil semua dan tidak boleh mengandung spasi.

# Tambahkan library

Untuk menambahkan libarary yang akan kita gunakan, buka dan edit file pubspec.yaml seperti dibawah ini :

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  http: ^0.12.0+2 
  toast: ^0.1.4 
  font_awesome_flutter: ^8.4.0
  dio: ^2.1.0
  path_provider: ^0.5.0+1
  simple_permissions: ^0.1.9

# Bikin class model

Untuk membuat object dari data Youtube Donwloader, bikin file baru beri nama youtube_model.dart, berikut ini snippet code-nya :

class Result {
  String id;
  String vid;
  String ajax;
  String ftype;
  String fquality;
  String audioName;
  String thumbnail;
  String duration;

  Result(
      {this.id,
      this.vid,
      this.ajax,
      this.ftype,
      this.fquality,
      this.audioName,
      this.thumbnail,
      this.duration});

  Result.convertResult(String inpute) {
    id = RegExp(r"\\n_id: '(.*?)',").firstMatch(inpute).group(1);
    vid = RegExp(r"v_id: '(.*?)',").firstMatch(inpute).group(1);
    audioName = RegExp(r"data_vtitle = \\" "(.*?)\\" ";").firstMatch(inpute).group(1).replaceAll(RegExp('"\\"|"'), "").replaceAll("\\", "");
    thumbnail = RegExp(r'<img src=\\"(.*?)\\"').firstMatch(inpute).group(1).replaceAll("\\", "");
    duration = RegExp(r'>Duration:.(.*?)<').firstMatch(inpute).group(1);
    fquality = "128";
    ajax = "1";
    ftype = ".mp3";
  }
}

# Halaman Utama

Selanjutnya untuk membuat halaman utama, buat file baru dengan nama youtube_konversi.dart, berikut ini snippet code-nya :

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'youtube_model.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:simple_permissions/simple_permissions.dart';
import 'package:toast/toast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';

class YoutubeKonversi extends StatefulWidget {
  @override
  _YoutubeKonversiState createState() => _YoutubeKonversiState();
}

class _YoutubeKonversiState extends State<YoutubeKonversi> {
  bool _allowWriteFile = false;
  TextEditingController videoURL = TextEditingController();
  Result video;
  bool isFetching = false;
  bool fetchSuccess = false;
  bool isDownloading = false;
  bool downloadsuccess = false;
  String status = "Unduh ";
  String progress = "";
  String lokasi = "";

  Map<String, String> headers = {
    "X-Requested-With": "XMLHttpRequest",
  };

  Map<String, String> body;

  void insertBody(String videoURL) {
    body = {"url": videoURL, "ajax": "1"};
  }

  Future<void> getInfo() async {
    insertBody(videoURL.text);
    setState(() {
      progress = "";
      lokasi = "";
      status = "Unduh";
      downloadsuccess = false;
      isDownloading = false;
      isFetching = true;
      fetchSuccess = false;
    });
    try {
      var response = await http.post("https://y2mate.com/id/analyze/ajax",
          body: body, headers: headers);

      video = Result.convertResult(response.body);
      setState(() {
        isFetching = false;
        fetchSuccess = true;
      });
    } catch (e) {
      print(e.toString());
      setState(() {
        isFetching = true;
        fetchSuccess = false;
      });
    }
    print("${video.thumbnail}\n${video.audioName}\n${video.vid}\n${video.id}");
  }

  Future<void> directURI(
      String _id, String vid, String ajax, String ftype, String quality) async {
    setState(() {
      isDownloading = true;
      status = "Mengunduh...";
    });
    try {
      var bodies = {
        "type": "youtube",
        "_id": _id,
        "v_id": vid,
        "ajax": ajax,
        "ftype": ftype.replaceAll(".", ""),
        "fquality": quality,
      };
      var response =
          await http.post("https://y2mate.com/id/convert", body: bodies);
      print(response.body);
      if (response.body.contains("Error:")) {
        Toast.show(
          "Tidak dapat mengunduh \n Silakan coba lagi nanti ...",
          context,
          duration: 4,
          textColor: Colors.white,
          gravity: Toast.BOTTOM,
        );
        setState(() {
          isDownloading = false;
        });
        return;
      }
      var directURL = RegExp(r'<a href=\\"(.*?)\\"')
          .firstMatch(response.body)
          .group(1)
          .replaceAll("\\", "");
      print("FIle Link :" + directURL);
      downloadVideo(directURL, video.audioName, video.ftype);
    } catch (e) {
      setState(() {
        isDownloading = false;
      });
      Toast.show(
        e.toString(),
        context,
        duration: 2,
        backgroundColor: Colors.red[300],
        textColor: Colors.black,
        gravity: Toast.BOTTOM,
      );
    }
  }

  Future<void> downloadVideo(
      String trackURL, String trackName, String format) async {
    if (!_allowWriteFile) {
      return null;
    }
    try {
      Dio dio = Dio();

      Response response = await dio.get(
        trackURL,
        onReceiveProgress: (rec, total) {
          setState(() {
            progress = ((rec / total) * 100).toStringAsFixed(0) + "%";
          });
        },
        options: Options(
          responseType: ResponseType.bytes,
          followRedirects: false,
        ),
      );
      print(response.headers);
      //getExternalStorageDirectory getApplicationDocumentsDirectory
      var directory = await getExternalStorageDirectory();
      var testdir = await Directory(
              '${directory.path}/io.github.rifkyfu32.konversi_youtube_mp3')
          .create(recursive: true);
      File file = File("${testdir.path}/" + trackName + format);
      var raf = file.openSync(mode: FileMode.write);
      raf.writeFromSync(response.data);
      await raf.close();
      setState(() {
        isDownloading = false;
        status = "Selesai ^_^";
        downloadsuccess = true;
        lokasi = "${testdir.path}/" + trackName + format;
      });
      print("${testdir.path}/" + trackName + format);
    } catch (e) {
      setState(() {
        isDownloading = false;
      });
      Toast.show(
        e.toString(),
        context,
        duration: 2,
        backgroundColor: Colors.red[300],
        textColor: Colors.black,
        gravity: Toast.BOTTOM,
      );
    }
  }

  @override
  void initState() {
    super.initState();
    _requestWritePermission();
  }

  _requestWritePermission() async {
    PermissionStatus permissionStatus =
        await SimplePermissions.requestPermission(
            Permission.WriteExternalStorage);

    if (permissionStatus == PermissionStatus.authorized) {
      setState(() {
        _allowWriteFile = true;
      });
    }
  }

  void nothingHere() {
    print("Tidak masalah");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: searchBar(),
        backgroundColor: Color.fromARGB(255, 30, 30, 30),
        centerTitle: true,
      ),
      body: bodyPart(),
    );
  }

  Widget bodyPart() {
    return Container(
      color: Color.fromARGB(255, 30, 30, 30),
      child: Center(
        child: isFetching
            ? progressScreen()
            : fetchSuccess
                ? downloadScreen()
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text(
                        "Konversi Youtube ke MP3",
                        style: TextStyle(color: Colors.white, fontSize: 18.0),
                      ),
                      SizedBox(
                        height: 5.0,
                      ),
                      Text(
                        "Api dari y2mate.com",
                        style: TextStyle(color: Colors.white, fontSize: 16.0),
                      ),
                      SizedBox(
                        height: 5.0,
                      ),
                      Icon(
                        FontAwesomeIcons.youtube,
                        color: Colors.redAccent,
                        size: 45.0,
                      ),
                    ],
                  ),
      ),
    );
  }

  Widget progressScreen() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        CircularProgressIndicator(),
        Padding(
          padding: const EdgeInsets.all(9.0),
          child: Text(
            'Mengambil Data ...',
            style: TextStyle(color: Colors.white, fontSize: 18.0),
          ),
        )
      ],
    );
  }

  Widget downloadScreen() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Container(
          margin: EdgeInsets.symmetric(horizontal: 20.0),
          height: 300.0,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.all(
              Radius.circular(19.0),
            ),
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Expanded(
                child: Image(
                  image: NetworkImage(video.thumbnail),
                ),
              ),
              SizedBox(
                height: 10.0,
              ),
              labelTitle("Judul : ", video.audioName),
              SizedBox(
                height: 8.0,
              ),
              labelTitle("Durasi : ", video.duration),
              SizedBox(
                height: 8.0,
              ),
              FlatButton(
                onPressed: () {
                  !downloadsuccess
                      ? directURI(video.id, video.vid, video.ajax, video.ftype,
                          video.fquality)
                      : nothingHere();
                },
                child: Container(
                  height: 40.0,
                  width: 200.0,
                  decoration: BoxDecoration(
                    color: downloadsuccess == true
                        ? Colors.greenAccent
                        : Colors.redAccent,
                    borderRadius: BorderRadius.all(
                      Radius.circular(
                        50.0,
                      ),
                    ),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        vertical: 4.0, horizontal: 10.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          status,
                          style: TextStyle(
                              color: downloadsuccess == true
                                  ? Colors.black87
                                  : Colors.white70,
                              fontSize: 16.0),
                        ),
                        SizedBox(
                          width: 12.0,
                        ),
                        Icon(
                          isDownloading
                              ? FontAwesomeIcons.spinner
                              : downloadsuccess
                                  ? FontAwesomeIcons.check
                                  : FontAwesomeIcons.download,
                          color: downloadsuccess == true
                              ? Colors.black87
                              : Colors.white70,
                          size: 20.0,
                        )
                      ],
                    ),
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.only(top: 8.0, bottom: 10.0),
                child: Text(
                  progress,
                  style: TextStyle(color: Colors.white),
                ),
              ),
              downloadsuccess
                  ? Card(
                      elevation: 3.0,
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Text('Lokasi berkas : ',
                                style: TextStyle(color: Colors.red)),
                            Padding(padding: EdgeInsets.only(bottom: 5.0)),
                            Text(lokasi,
                                style: TextStyle(color: Colors.black87))
                          ],
                        ),
                      ),
                    )
                  : SizedBox(
                      height: 8.0,
                    )
            ],
          ),
        ),
      ],
    );
  }

  Widget labelTitle(String title, String inpute) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Flexible(
          child: Text(
            title,
            style: TextStyle(
                color: Colors.redAccent,
                fontSize: 17.0,
                fontWeight: FontWeight.bold),
          ),
        ),
        Flexible(
          child: Text(
            inpute,
            style: TextStyle(
                color: Colors.white,
                fontSize: 16.0,
                fontWeight: FontWeight.normal),
          ),
        ),
      ],
    );
  }

  Widget searchBar() {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 4.0),
      margin: EdgeInsets.symmetric(
        horizontal: 20.0,
        vertical: 8.0,
      ),
      decoration: BoxDecoration(
        color: Color.fromARGB(100, 255, 255, 255),
        borderRadius: BorderRadius.all(
          Radius.circular(10.0),
        ),
      ),
      child: Row(
        children: <Widget>[
          Flexible(
            flex: 1,
            child: TextFormField(
              controller: videoURL,
              style: TextStyle(color: Colors.white),
              decoration: InputDecoration(
                hintText: "Video URL ...",
                border: InputBorder.none,
                hintStyle: TextStyle(color: Colors.white70),
                icon: IconButton(
                  onPressed: () {
                    getInfo();
                  },
                  icon: Icon(
                    Icons.search,
                    color: Colors.white,
                  ),
                ),
              ),
              keyboardType: TextInputType.url,
              autofocus: true,
              onFieldSubmitted: (String value) {
                getInfo();
              },
            ),
          ),
        ],
      ),
    );
  }
}

# Sentuhan Terakhir

Selanjutnya kita ubah main.dart, hapus semua kemudian ganti dengan snippet code berikut ini :

import 'package:flutter/material.dart';
import 'youtube_konversi.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Konversi Youtube ke MP3',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        accentColor: Colors.red[400]
      ),
      debugShowCheckedModeBanner: false,
      home: YoutubeKonversi(),
    );
  }
}

Selesai. sekarang jalankan aplikasi Konverter Youtube to MP3 dengan menekan tombol F5, namun sebelumnya jangan lupa start Android Emulator atau iOS Simulator.

Hasilnya akan terlihat seperti berikut ini:

gambar gambar gambar

Hasil aplikasi bisa didownload disini