api를 이용해서 유튜브 영상 재생
HTTP기반의 api는 REST API, GraphQL, gRPC등이 존재한다 여기서 주로 사용하는것은 REST api를 이용한다 rest api에는 get, post, delete, put등 메서드를 이용한다 이 메서드들을 이요해서 CRUD 연산을 한다
플러터에서 HTTP요청
flutter pub add dio를 입력해서 플러그인을 설치한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Dio _dio = Dio();
String _data = "데이터 없음";
// GET 요청 실행
void _fetchData() async {
try {
Response response = await _dio.get("https://jsonplaceholder.typicode.com/todos/1");
setState(() {
_data = response.data.toString();
});
} catch (e) {
setState(() {
_data = "오류 발생: $e";
});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Dio GET 요청 예제")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _fetchData,
child: Text("데이터 가져오기"),
),
SizedBox(height: 20),
Text(_data, textAlign: TextAlign.center),
],
),
),
),
);
}
}
async와 await를 통해서 비동기 함수를 만들었고 dio 객체를 생성한뒤에 dio의 get메서드와 url을 통해서 데이터를 받는다 받은후 string으로 변경해서 화면에 출력하는 코드이다
JSON
HTTP 요청에서 body를 구성할 때 xml, json으로 나뉜다 하지만 요즘은 대부분 json을 주로 이용한다
1
2
3
4
5
{
'name': 'code',
'languages': ['c','java','swift'],
'age': 2
}
위 형태가 json의 형태이다
api 설정하기
유튜브 api를 사용할려면 이전에 사용한 api 토큰을 이용하면 되지만 약간의 추가 설정이 필요하다
검색에 youtube를 검색 후 이동한다
사용을 눌러 활성화 시킨다
프로젝트
1. pubspec.yaml 설정하기
flutter pub add youtube_player_flutter와 flutter pub add dio를 이용해서 2개의 플러그인을 설치한다
1
2
3
4
5
6
7
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dio: ^5.8.0
youtube_player_flutter: ^9.1.1
2. VideoModel 구현
HTTP 요청의 응답을 담을 모델 클래스를 구현해야한다 유튜브 api를 이용하면 많은 정보를 가져올 수 있다 그중에서 동영상 id와 제목만 가져와서 활용한다
model 폴더에 video_model 파일을 생성한다
1
2
3
4
5
6
7
8
9
class VideoModel {
final String id;
final String title;
VideoModel({
required this.id,
required this.title,
});
}
3. Youtube player 위젯
lib -> component폴더 생성 -> custom_youtube_player.dart 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:flutter/material.dart';
import 'package:useapi/model/video_model.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class CustomYoutubePlayer extends StatefulWidget{
final VideoModel videoModel;
const CustomYoutubePlayer({
required this.videoModel,
Key? key
}) : super (key: key);
@override
State<CustomYoutubePlayer> createState() => _CustomYoutubePlayerState();
}
class _CustomYoutubePlayerState extends State<CustomYoutubePlayer> {
@override
Widget build(BuildContext) {
return Container();
}
}
statefull로 커스텀 플레이어 위젯을 정의한다
이제 youtubeplayer를 조정하기위해서 YoutubePlayerController를 이용해야한다 initState( )함수를 이용해서 초기화하고 dispose( )함수를 이용해서 폐기한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import 'package:flutter/material.dart';
import 'package:useapi/model/video_model.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class CustomYoutubePlayer extends StatefulWidget{
final VideoModel videoModel;
const CustomYoutubePlayer({
required this.videoModel,
Key? key
}) : super (key: key);
@override
State<CustomYoutubePlayer> createState() => _CustomYoutubePlayerState();
}
class _CustomYoutubePlayerState extends State<CustomYoutubePlayer> {
YoutubePlayerController? controller;
@override
void initState() {
super.initState();
controller = YoutubePlayerController(
initialVideoId: widget.videoModel.id,
flags: YoutubePlayerFlags(
autoPlay: false, //자동실행 안함
),
);
}
@override
Widget build(BuildContext) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, //가로 늘림
children: [
YoutubePlayer(
controller: controller!,
showVideoProgressIndicator: true,
),
const SizedBox(height: 16,),
Padding(padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
widget.videoModel.title
),
),
const SizedBox(height: 16)
],
);
}
@override
void dispose() {
super.dispose();
controller!.dispose();
}
}
4. 유튜브 위젯 사용
만든 위젯을 이용해서 homescreen에 출력한다 homescreen.dart에서 커스텀 플레이어를 사용하고 매개변수로 만든 모델을 넣어서 실행시킨다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'package:flutter/material.dart';
import 'package:useapi/component/custom_youtube_player.dart';
import 'package:useapi/model/video_model.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: CustomYoutubePlayer(
videoModel: VideoModel(
id: 'ywU-E2n2loE',
title: '전스탭 야외취침이라는 사상 초유의 사건!',
)
)
);
}
}
5. youtubeRepository 구현
지금은 직접 제공한 동영상 id를 통해서 제공하고 있지만 이렇게 하면 일일이 영상을 넣기 힘들기 때문에 최신 영상을 불러오도록 Dio를 이용해서 HTTP요청을 보낸다
lib - const - api.dart위치에 파일을 생성하고 이곳에 사용할 상수를 모두 정의한다
1
2
3
4
5
const API_KEY = '키입력';
const YOUTUBE_API_BASE_URL = 'https://youtube.googleapis.com/v3/search';
const CHANNEL_ID = 'UCUj6rrhMTR9pipbAWBAMvUQ';
위 상수에서 채널 id는 해당 채널 메인의 주소 마지막을 보면 @~~~ 이렇게 되어있는데 이 id를 사용하면 안된다 invalid argument 오류가 계속 발생한다 채널 id를 따로 찾아야했다
postman으로 요청을 한번 보내봤다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{
"kind": "youtube#searchListResponse",
"etag": "QAH_jr6919qTbpa0IzENYwaV7zk",
"nextPageToken": "CAMQAA",
"regionCode": "US",
"pageInfo": {
"totalResults": 471271,
"resultsPerPage": 3
},
"items": [
{
"kind": "youtube#searchResult",
"etag": "oZsP9ih7Q9uC6jJWLgrfz7kTvfo",
"id": {
"kind": "youtube#video",
"videoId": "Y35qt9v126Q"
},
"snippet": {
"publishedAt": "2025-01-30T10:00:37Z",
"channelId": "UCUj6rrhMTR9pipbAWBAMvUQ",
"title": "상향평준화된 요즘 귤 먹방",
"description": "같이 보면 좋은 추천 영상 https://youtu.be/PTJpv8cdvZ4?si=0BRlrB4LGDlig46E ▷'침착맨의 식욕감퇴 다이어트 먹방' 모아보기 ...",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/Y35qt9v126Q/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/Y35qt9v126Q/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/Y35qt9v126Q/hqdefault.jpg",
"width": 480,
"height": 360
}
},
"channelTitle": "침착맨",
"liveBroadcastContent": "none",
"publishTime": "2025-01-30T10:00:37Z"
}
},
{
"kind": "youtube#searchResult",
"etag": "tHbTyjOhfyWr0sL6lTF5AnHaAcE",
"id": {
"kind": "youtube#video",
"videoId": "5XSsiUixsCU"
},
"snippet": {
"publishedAt": "2025-01-30T08:00:06Z",
"channelId": "UCUj6rrhMTR9pipbAWBAMvUQ",
"title": "티라노 약점",
"description": "이 클립이 나온 영상 (https://youtu.be/kOl27s1mgrg?si=n8jbbVGdfSauipXt) • 2022년 01월 13일 1부 방송분 ...",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/5XSsiUixsCU/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/5XSsiUixsCU/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/5XSsiUixsCU/hqdefault.jpg",
"width": 480,
"height": 360
}
},
"channelTitle": "침착맨",
"liveBroadcastContent": "none",
"publishTime": "2025-01-30T08:00:06Z"
}
},
{
"kind": "youtube#searchResult",
"etag": "XvtzikoQoGOId26aMx1j99oMyXw",
"id": {
"kind": "youtube#video",
"videoId": "KkhDe04Gyw8"
},
"snippet": {
"publishedAt": "2025-01-29T10:00:19Z",
"channelId": "UCUj6rrhMTR9pipbAWBAMvUQ",
"title": "2025년의 침투부 얼굴 그리기",
"description": "같이 보면 좋은 추천 영상 https://youtu.be/e7bXjrtMdgE?si=AwctXDxEE7WClvyF ▷'침착맨의 일상재롱' 모아보기 ...",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/KkhDe04Gyw8/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/KkhDe04Gyw8/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/KkhDe04Gyw8/hqdefault.jpg",
"width": 480,
"height": 360
}
},
"channelTitle": "침착맨",
"liveBroadcastContent": "none",
"publishTime": "2025-01-29T10:00:19Z"
}
}
]
}
위 형식으로 응답이 온다 위 응답에서 필요한 데이터는 items 내부의 title과 id이다
id를 가져오기 위해서는 item[id][videoId]를 실행하고 title을 가져오기 위해서는 item[snippet][title]을 실행하면 된다
이제 json데이터를 받으면 List<videomodel>로 변환한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//respository/youtube_respository.dart
import 'package:useapi/const/api.dart';
import 'package:dio/dio.dart';
import 'package:useapi/model/video_model.dart';
class YoutubeRepository {
static Future<List<VideoModel>> getVideos() async {
final resp = await Dio().get(
YOUTUBE_API_BASE_URL,
queryParameters: {
'channelId' : CHANNEL_ID,
'maxResults' : 10,
'key' : API_KEY,
'part' : 'snippet',
'order' : 'date',
},
);
final listWithData = resp.data['items'].where(
(item) =>
item?['id']?['videoId'] != null && item?['snippet']?['title'] != null
); //id, title이 Null이 아닌 값만 추출
return listWithData
.map<VideoModel>(
(item) => VideoModel(id: item['id']['videoId'], title: item['snippet']['title'])
).toList();
}
}
6. ListView구현
이제 받은 데이터를 보여줄 위젯을 생성해야한다 그중 ListView를 이용해서 받은 데이터를 보여준다 getVideos() 비동기 함수로 데이터를 가져와야하니 FutureBuilder를 이용한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import 'package:flutter/material.dart';
import 'package:useapi/component/custom_youtube_player.dart';
import 'package:useapi/model/video_model.dart';
import 'package:useapi/repository/youtube_repository.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
centerTitle: true,
title: Text(
'침착맨',
),
backgroundColor: Colors.black,
),
body: FutureBuilder<List<VideoModel>>(
future: YoutubeRepository.getVideos(),
builder: (context, snapshot){
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
)
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
physics: BouncingScrollPhysics(), //아래로 당겨서 튕기는 애니메이션 추가
children: snapshot.data!
.map((e) => CustomYoutubePlayer(videoModel: e))
.toList()
);
}
)
);
}
}
동영상 10개를 리스트 형식으로 출력한다
7. 새로고침 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import 'package:flutter/material.dart';
import 'package:useapi/component/custom_youtube_player.dart';
import 'package:useapi/model/video_model.dart';
import 'package:useapi/repository/youtube_repository.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
centerTitle: true,
title: Text(
'침착맨',
),
backgroundColor: Colors.black,
),
body: FutureBuilder<List<VideoModel>>(
future: YoutubeRepository.getVideos(),
builder: (context, snapshot){
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
)
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return RefreshIndicator(
onRefresh: ()async {
setState(() {
});
},
child: ListView(
physics: BouncingScrollPhysics(), //아래로 당겨서 튕기는 애니메이션 추가
children: snapshot.data!
.map((e) => CustomYoutubePlayer(videoModel: e))
.toList()
)
);
}
)
);
}
}