0

我正在尝试使用 Youtube 视频创建类似 Tinder 的滑动功能。我将详细描述我想要实现的目标。

逐步细分:

  1. 使用Youtube Data API v3获取 Youtube 视频。

youtube _model.dart

// To parse this JSON data, do
//
//     final youtubeSearchVideos = youtubeSearchVideosFromJson(jsonString);

import 'dart:convert';

YoutubeSearchVideos youtubeSearchVideosFromJson(String str) =>
    YoutubeSearchVideos.fromJson(json.decode(str));

String youtubeSearchVideosToJson(YoutubeSearchVideos data) =>
    json.encode(data.toJson());

class YoutubeSearchVideos {
  YoutubeSearchVideos({
    required this.kind,
    required this.etag,
    this.nextPageToken,
    this.prevPageToken,
    required this.regionCode,
    required this.pageInfo,
    required this.items,
  });

  String kind;
  String etag;
  String? nextPageToken;
  String? prevPageToken;
  String regionCode;
  PageInfo pageInfo;
  List<Item> items;

  factory YoutubeSearchVideos.fromJson(Map<String, dynamic> json) =>
      YoutubeSearchVideos(
        kind: json["kind"],
        etag: json["etag"],
        nextPageToken: json["nextPageToken"],
        prevPageToken: json["prevPageToken"],
        regionCode: json["regionCode"],
        pageInfo: PageInfo.fromJson(json["pageInfo"]),
        items: List<Item>.from(json["items"].map((x) => Item.fromJson(x))),
      );

  Map<String, dynamic> toJson() => {
        "kind": kind,
        "etag": etag,
        "nextPageToken": nextPageToken,
        "prevPageToken": prevPageToken,
        "regionCode": regionCode,
        "pageInfo": pageInfo.toJson(),
        "items": List<dynamic>.from(items.map((x) => x.toJson())),
      };
}

class Item {
  Item({
    required this.kind,
    required this.etag,
    required this.id,
    required this.snippet,
  });

  String kind;
  String etag;
  Id id;
  Snippet snippet;

  factory Item.fromJson(Map<String, dynamic> json) => Item(
        kind: json["kind"],
        etag: json["etag"],
        id: Id.fromJson(json["id"]),
        snippet: Snippet.fromJson(json["snippet"]),
      );

  Map<String, dynamic> toJson() => {
        "kind": kind,
        "etag": etag,
        "id": id.toJson(),
        "snippet": snippet.toJson(),
      };
}

class Id {
  Id({
    required this.kind,
    required this.videoId,
  });

  String kind;
  String videoId;

  factory Id.fromJson(Map<String, dynamic> json) => Id(
        kind: json["kind"],
        videoId: json["videoId"],
      );

  Map<String, dynamic> toJson() => {
        "kind": kind,
        "videoId": videoId,
      };
}

class Snippet {
  Snippet({
    required this.publishedAt,
    required this.channelId,
    required this.title,
    required this.description,
    required this.thumbnails,
    required this.channelTitle,
    required this.liveBroadcastContent,
    required this.publishTime,
  });

  DateTime publishedAt;
  String channelId;
  String title;
  String description;
  Thumbnails thumbnails;
  String channelTitle;
  String liveBroadcastContent;
  DateTime publishTime;

  factory Snippet.fromJson(Map<String, dynamic> json) => Snippet(
        publishedAt: DateTime.parse(json["publishedAt"]),
        channelId: json["channelId"],
        title: json["title"],
        description: json["description"],
        thumbnails: Thumbnails.fromJson(json["thumbnails"]),
        channelTitle: json["channelTitle"],
        liveBroadcastContent: json["liveBroadcastContent"],
        publishTime: DateTime.parse(json["publishTime"]),
      );

  Map<String, dynamic> toJson() => {
        "publishedAt": publishedAt.toIso8601String(),
        "channelId": channelId,
        "title": title,
        "description": description,
        "thumbnails": thumbnails.toJson(),
        "channelTitle": channelTitle,
        "liveBroadcastContent": liveBroadcastContent,
        "publishTime": publishTime.toIso8601String(),
      };
}

class Thumbnails {
  Thumbnails({
    required this.thumbnailsDefault,
    required this.medium,
    required this.high,
  });

  Default thumbnailsDefault;
  Default medium;
  Default high;

  factory Thumbnails.fromJson(Map<String, dynamic> json) => Thumbnails(
        thumbnailsDefault: Default.fromJson(json["default"]),
        medium: Default.fromJson(json["medium"]),
        high: Default.fromJson(json["high"]),
      );

  Map<String, dynamic> toJson() => {
        "default": thumbnailsDefault.toJson(),
        "medium": medium.toJson(),
        "high": high.toJson(),
      };
}

class Default {
  Default({
    required this.url,
    required this.width,
    required this.height,
  });

  String url;
  int width;
  int height;

  factory Default.fromJson(Map<String, dynamic> json) => Default(
        url: json["url"],
        width: json["width"],
        height: json["height"],
      );

  Map<String, dynamic> toJson() => {
        "url": url,
        "width": width,
        "height": height,
      };
}

class PageInfo {
  PageInfo({
    required this.totalResults,
    required this.resultsPerPage,
  });

  int totalResults;
  int resultsPerPage;

  factory PageInfo.fromJson(Map<String, dynamic> json) => PageInfo(
        totalResults: json["totalResults"],
        resultsPerPage: json["resultsPerPage"],
      );

  Map<String, dynamic> toJson() => {
        "totalResults": totalResults,
        "resultsPerPage": resultsPerPage,
      };
}

youtube_api_service.dart

    import 'package:http/http.dart' as http;
    import 'package:starcast_intros/models/youtube_search.dart';
    import 'package:starcast_intros/private_keys.dart';
    
    class YoutubeApi {
      static const String youtubeAPI =
          'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=5&q=surfing&type=video&videoDefinition=standard&videoDimension=2d&videoDuration=short&videoEmbeddable=true&key=$YOUTUBE_DATA_API_KEY';
    
      Future<YoutubeSearchVideos> fetchVideos() async {
        try {
          final response = await http.get(Uri.parse(youtubeAPI));
    
          if (response.statusCode == 200) {
            return youtubeSearchVideosFromJson(response.body);
          }
    
          throw Exception('Failed to fetch videos ${response.body}');
        } catch (e) {
          print(e);
          throw Exception('Failed to fetch videos $e');
        }
      }
    }

2. After retrieving the list of youtube video IDs from the API, render the Youtube videos like Tinder cards which can be swiped left or right.

import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:starcast_intros/models/youtube_search.dart';
import 'package:starcast_intros/services/youtube_api.dart';
import 'package:tcard/tcard.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);
  static const HOME = 'Home';

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late Future<YoutubeSearchVideos> futureVideos;

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

    final youtubeAPI = YoutubeApi();
    futureVideos = youtubeAPI.fetchVideos();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<YoutubeSearchVideos>(
      future: futureVideos,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          List<Widget> cards = List.generate(
            snapshot.data!.items.length,
            (int index) {
              YoutubePlayerController _controller = YoutubePlayerController(
                initialVideoId: snapshot.data!.items[index].id.videoId,
                flags: YoutubePlayerFlags(
                  autoPlay: false,
                  mute: true,
                  isLive: false,
                  disableDragSeek: true,
                  loop: false,
                  forceHD: false,
                ),
              );

              return Container(
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(16.0),
                  boxShadow: [
                    BoxShadow(
                      offset: Offset(0, 17),
                      blurRadius: 23.0,
                      spreadRadius: -13.0,
                      color: Colors.black54,
                    )
                  ],
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(16.0),
                  child: YoutubePlayer(
                    controller: _controller,
                  ),
                ),
              );
            },
          );

          return TCard(
            size: Size(
              MediaQuery.of(context).size.width,
              MediaQuery.of(context).size.height,
            ),
            cards: cards,
          );
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }

        // By default, show a loading spinner.
        return SpinKitDoubleBounce(
          color: Theme.of(context).accentColor,
          size: 75.0,
        );
      },
    );
  }
}
  1. 请注意,您需要一个 Youtube API 密钥(使用 Google 控制台创建)来检索视频列表。我正在使用 Youtube 搜索 API。如果您不想发出请求或创建 API 密钥,可能可以使用下面给出的 JSON:

{ “种类”:“youtube#searchListResponse”,“etag”:“E2FpjhO0gVzn8gmf9Q1VSJ72Rwk”,“nextPageToken”:“CAUCAA”,
片段”:{“publishedAt”:“2010-08-13T02:10:28Z”,“channelId”:“UCTYHNSWYy4jCSCj1Q1Fq0ew”,“标题”:“安迪·艾恩斯 - 我冲浪是因为短片”,“描述”:“安迪·艾恩斯是世界上最伟大的冲浪者之一。3 次世界冠军因与凯利·斯莱特 (Kelly Slater) 的史诗般的战斗而闻名。但除了所有的胜利......”,“缩略图”:{“默认”:{“url”:“https://i.ytimg.com/vi/4uwtqRBE4Kk/default.jpg”,“宽度”:120, “高度”:90 },“中”:{ “url”:“https://i.ytimg.com/vi/4uwtqRBE4Kk/mqdefault.jpg”,“宽度”:320,“高度”:180 },“高”:{“网址”:“https://i.ytimg.com/vi/4uwtqRBE4Kk/hqdefault.jpg”,“宽度”

一旦卡片与不同的视频堆叠在一起,我在顶部刷卡。我一滑动,第一张卡片中的视频就会出现在下面的卡片中(第二张卡片)。我预计第二个视频会在第二张卡中播放,因为所有视频 ID 都不同。

如果我只是稍微拖动并按住它,我可以在第二张卡片中看到第二个视频的缩略图。但是,只要我向右滑动,第二张卡片(第二张视频)中的视频就会被第一张卡片(第一张视频)中的视频替换。

如此重复直到最后一张牌。

任何帮助解决这个问题将不胜感激。感谢期待。

干杯。

4

1 回答 1

1

为每张 YouTube 卡片添加唯一的密钥,您可以使用 YouTube ID 作为密钥

何时使用密钥

于 2021-07-28T18:15:53.457 回答