YouTube Music Downloader - Flutter 크로스플랫폼 앱
YTM App은 YouTube 영상을 고품질 MP3로 다운로드하는 크로스플랫폼 모바일 앱입니다.
| 플랫폼 | 지원 | 배포 방식 |
|---|---|---|
| Android | ✅ | APK 다운로드, GitHub Releases |
| iOS | ✅ | TestFlight, 웹앱 |
| Web | ✅ | PWA |
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 🏠 Home │ │ ⬇️ Downloads │ │ 🎵 Player │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ │ │ │ │ │
│ [🔗 URL 입력] │ │ ▶ Track 1 │ │ ┌───────┐ │
│ │ │ ████░░ 70% │ │ │ Album │ │
│ [🔍 검색...] │ │ │ │ │ Art │ │
│ │ │ ✓ Track 2 │ │ └───────┘ │
│ ───────────── │ │ 완료됨 │ │ │
│ Recent: │ │ │ │ Track Title │
│ • Stronger │ │ ▶ Track 3 │ │ Artist Name │
│ • Heartless │ │ ██░░░░ 30% │ │ │
│ • Gold Digger │ │ │ │ ──●───── 2:30 │
│ │ │ │ │ ◀◀ ▶ ▶▶ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
| 기능 | 설명 |
|---|---|
| 🔗 URL 다운로드 | YouTube URL 붙여넣기로 즉시 다운로드 |
| 🔍 검색 | 앱 내에서 YouTube 검색 |
| ⬇️ 다운로드 관리 | 진행률 표시, 큐 관리 |
| 🎵 내장 플레이어 | 다운로드한 음악 바로 재생 |
| 📁 라이브러리 | 다운로드 히스토리 관리 |
| 기능 | Free | Pro |
|---|---|---|
| 일일 다운로드 | 3개 | 무제한 |
| 음질 | 128kbps | 320kbps |
| 광고 | 있음 | 없음 |
| 플레이리스트 다운로드 | ❌ | ✅ |
| 백그라운드 다운로드 | ❌ | ✅ |
Framework : Flutter 3.x
State Management : Riverpod 2.x
YouTube API : youtube_explode_dart
Audio Player : just_audio + audio_service
Audio Conversion : ffmpeg_kit_flutter
Local Storage : Hive
HTTP Client : Dio
ytm-app/
├── lib/
│ ├── main.dart # 앱 진입점
│ │
│ ├── core/ # 핵심 모듈
│ │ ├── api/
│ │ │ └── youtube_api.dart # YouTube 데이터 조회
│ │ ├── models/
│ │ │ └── track.dart # Track 데이터 모델
│ │ ├── services/
│ │ │ └── download_service.dart # 다운로드 + 변환 서비스
│ │ └── utils/ # 유틸리티 함수
│ │
│ ├── features/ # 기능별 모듈
│ │ ├── home/ # 홈 화면
│ │ │ ├── home_screen.dart
│ │ │ └── widgets/
│ │ │ └── recent_downloads.dart
│ │ │
│ │ ├── download/ # 다운로드 관리
│ │ │ └── download_screen.dart
│ │ │
│ │ ├── player/ # 음악 플레이어
│ │ │ └── player_screen.dart
│ │ │
│ │ └── settings/ # 설정
│ │ └── settings_screen.dart
│ │
│ └── shared/ # 공유 위젯
│ └── widgets/
│
├── assets/ # 에셋 파일
│ ├── images/
│ └── fonts/
│
├── android/ # Android 네이티브
├── ios/ # iOS 네이티브
├── pubspec.yaml # 의존성 정의
└── README.md
- Flutter SDK 3.0+
- Dart SDK 3.0+
- Android Studio / Xcode
# 1. 저장소 클론
git clone https://github.com/shinjadong/ytm-app.git
cd ytm-app
# 2. 의존성 설치
flutter pub get
# 3. Hive 어댑터 생성
flutter pub run build_runner build --delete-conflicting-outputs
# 4. 실행
flutter runVS Code 확장:
- Flutter
- Dart
- Flutter Riverpod Snippets
Android Studio 플러그인:
- Flutter
- Dart
# Debug APK
flutter build apk --debug
# Release APK
flutter build apk --release
# Split APKs (용량 최적화)
flutter build apk --split-per-abi출력 경로: build/app/outputs/flutter-apk/
flutter build appbundle --release# 시뮬레이터용
flutter build ios --debug
# TestFlight/App Store용 (Mac 필요)
flutter build ios --releaseflutter build web --release// Provider 정의
final downloadServiceProvider = Provider((ref) => DownloadService());
final downloadsProvider = StateNotifierProvider<DownloadsNotifier, List<Track>>((ref) {
return DownloadsNotifier(ref.read(downloadServiceProvider));
});
// 사용
class DownloadScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final downloads = ref.watch(downloadsProvider);
// ...
}
}┌─────────────────────────────────────────────────────────┐
│ UI Layer │
│ (HomeScreen, DownloadScreen, PlayerScreen, Settings) │
└─────────────────────────┬───────────────────────────────┘
│ ref.watch() / ref.read()
┌─────────────────────────▼───────────────────────────────┐
│ State Management │
│ (Riverpod Providers/Notifiers) │
└─────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ Service Layer │
│ (YouTubeAPI, DownloadService, AudioService) │
└─────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ Data Layer │
│ (youtube_explode_dart, Hive, Backend API, FFmpeg) │
└─────────────────────────────────────────────────────────┘
// lib/core/api/backend_api.dart
class BackendApi {
static const baseUrl = 'https://api.yourapp.com';
final Dio _dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: Duration(seconds: 10),
));
// 토큰 설정
void setToken(String token) {
_dio.options.headers['Authorization'] = 'Bearer $token';
}
// 영상 정보 조회
Future<Track> getVideoInfo(String url) async {
final response = await _dio.get('/api/videos/info',
queryParameters: {'url': url}
);
return Track.fromJson(response.data);
}
// 다운로드 요청
Future<Download> requestDownload(String videoId) async {
final response = await _dio.post('/api/downloads',
data: {'video_id': videoId}
);
return Download.fromJson(response.data);
}
}// youtube_explode_dart 직접 사용
class YouTubeApi {
final YoutubeExplode _yt = YoutubeExplode();
Future<Track> getVideoInfo(String url) async {
final video = await _yt.videos.get(url);
return Track(
id: video.id.value,
title: video.title,
artist: video.author,
// ...
);
}
}// lib/core/config/env.dart
class Env {
static const apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:8000',
);
static const isProduction = bool.fromEnvironment('PRODUCTION');
}flutter run --dart-define=API_URL=https://api.yourapp.com
flutter build apk --dart-define=API_URL=https://api.yourapp.com --dart-define=PRODUCTION=true# 유닛 테스트
flutter test
# 통합 테스트
flutter test integration_test/
# 커버리지
flutter test --coverageProprietary - All rights reserved
- Backend (FastAPI): ytm-backend
- Shell Script: ytm