我正在尝试使用 Youtube 视频创建类似 Tinder 的滑动功能。我将详细描述我想要实现的目标。
逐步细分:
- 使用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,
);
},
);
}
}
- 请注意,您需要一个 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 都不同。
如果我只是稍微拖动并按住它,我可以在第二张卡片中看到第二个视频的缩略图。但是,只要我向右滑动,第二张卡片(第二张视频)中的视频就会被第一张卡片(第一张视频)中的视频替换。
如此重复直到最后一张牌。
任何帮助解决这个问题将不胜感激。感谢期待。
干杯。