Post

api를 이용해서 유튜브 영상 재생

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_flutterflutter 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()
            )
          );
        }
      )
    );
  }
}
This post is licensed under CC BY 4.0 by the author.