En este post voy a explicar cómo obtener los videos subidos a un canal youtube desde una aplicación web desarrollada con spring mvc. También explicare como obtener información relacionada al video: duración, cantidad de reproducciones… y como hacer un paginador del listado de videos.
Para todo esto vamos a utilizar la api que nos provee google: YouTube Data API (v3). Esta api nos permite incorporar funcionalidades de youtube en nuestra propia aplicación. Se puede utilizar la api para buscar videos, insertar, actualizar y borrar recursos tales como videos o listas de reproducciones de video.
Contenido:
- Registrar nuestra aplicación para obtener una clave publica de google.
- Definición del modelo para convertir la data json a objetos java.
- Servicio spring que contendrá la api necesaria para la búsqueda de videos.
- Definición del controlador que recibirá las peticiones realizadas por el usuario.
- Diseño de la vista para mostrar la lista de videos de manera paginada.
- Vista de la aplicación.
1. Registrar nuestra aplicación para obtener una clave publica de google.
2. Definición del modelo para convertir la data json a objetos java: Vamos a crear dos clases java, una para manejar la información relacionada a la lista de videos retornada por google y otra clase para manejar la información del video.
public class VideoYoutube {
private String videoId;
private Date publischedAt;
private String channelId;
private String title;
private String description;
private String thumbnail;
private String channelTitle;
private String duration;
private Long viewCount;
public VideoYoutube() {
this.videoId = "";
this.publischedAt = null;
this.channelId = "";
this.title = "";
this.description = "";
this.thumbnail = "";
this.channelTitle = "";
this.duration = "";
this.viewCount = null;
}
public VideoYoutube(String videoId, Date publischedAt, String channelId,
String title, String description, String thumbnail,
String channelTitle, String duration, Long viewCount) {
super();
this.videoId = videoId;
this.publischedAt = publischedAt;
this.channelId = channelId;
this.title = title;
this.description = description;
this.thumbnail = thumbnail;
this.channelTitle = channelTitle;
this.duration = duration;
this.viewCount = viewCount;
}
public String getVideoId() {
return videoId;
}
public void setVideoId(String videoId) {
this.videoId = videoId;
}
public Date getPublischedAt() {
return publischedAt;
}
public void setPublischedAt(Date publischedAt) {
this.publischedAt = publischedAt;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
public String getChannelTitle() {
return channelTitle;
}
public void setChannelTitle(String channelTitle) {
this.channelTitle = channelTitle;
}
public String getDuration() {
if (duration != null && !duration.equals("")) {
StringBuilder strPeriod = new StringBuilder();
PeriodFormatter formatter = ISOPeriodFormat.standard();
Period period = formatter.parsePeriod(duration);
if (period.getHours() > 0) {
strPeriod.append(period.getHours()).append(":");
}
if (period.getMinutes() < 10) {
strPeriod.append("0").append(period.getMinutes()).append(":");
} else {
strPeriod.append(period.getMinutes()).append(":");
}
if (period.getSeconds() < 10) {
strPeriod.append("0").append(period.getSeconds());
} else {
strPeriod.append(period.getSeconds());
}
return strPeriod.toString();
}
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
public Long getViewCount() {
return viewCount;
}
public void setViewCount(Long viewCount) {
this.viewCount = viewCount;
}
}
public class PageResultYoutube {
private String nextPageToken;
private String prevPageToken;
private int totalResults;
private int resultsPerPage;
private Map videosResults;
public PageResultYoutube(String nextPageToken, String prevPageToken,
int totalResults, int resultsPerPage,
Map videosResults) {
super();
this.nextPageToken = nextPageToken;
this.prevPageToken = prevPageToken;
this.totalResults = totalResults;
this.resultsPerPage = resultsPerPage;
this.videosResults = videosResults;
}
public PageResultYoutube(String dataJson) {
JSONObject jo = new JSONObject(dataJson);
if (jo.has("nextPageToken")) {
nextPageToken = jo.getString("nextPageToken");
}
if (jo.has("prevPageToken")) {
prevPageToken = jo.getString("prevPageToken");
}
if (jo.has("pageInfo")) {
JSONObject pageInfo = jo.getJSONObject("pageInfo");
totalResults = pageInfo.getInt("totalResults");
resultsPerPage = pageInfo.getInt("resultsPerPage");
}
videosResults = new HashMap();
if (jo.has("items")) {
JSONArray items = jo.getJSONArray("items");
VideoYoutube video;
for (int i = 0; i < items.length(); i++) {
video = new VideoYoutube();
JSONObject item = items.getJSONObject(i);
JSONObject id = item.getJSONObject("id");
JSONObject snippet = item.getJSONObject("snippet");
video.setVideoId(id.getString("videoId"));
video.setChannelId(snippet.getString("channelId"));
video.setChannelTitle(snippet.getString("channelTitle"));
video.setDescription(snippet.getString("description"));
video.setPublischedAt(new DateTime(snippet
.getString("publishedAt")).toDate());
video.setThumbnail(snippet.getJSONObject("thumbnails")
.getJSONObject("default").getString("url"));
video.setTitle(snippet.getString("title"));
videosResults.put(video.getVideoId(), video);
}
}
}
public String getVideoIdsSeparetedByComma() {
StringBuilder str = new StringBuilder();
if (videosResults != null && videosResults.size() > 0) {
for (Entry entry : videosResults.entrySet()) {
str.append(entry.getKey()).append(",");
}
}// XXX fix validation
String finalStr = str.toString();
finalStr = finalStr.substring(0, finalStr.toString().length() - 1);
return finalStr;
}
public void addVideo(VideoYoutube video) {
videosResults.put(video.getVideoId(), video);
}
public String getNextPageToken() {
return nextPageToken;
}
public void setNextPageToken(String nextPageToken) {
this.nextPageToken = nextPageToken;
}
public String getPrevPageToken() {
return prevPageToken;
}
public void setPrevPageToken(String prevPageToken) {
this.prevPageToken = prevPageToken;
}
public int getTotalResults() {
return totalResults;
}
public void setTotalResults(int totalResults) {
this.totalResults = totalResults;
}
public int getResultsPerPage() {
return resultsPerPage;
}
public void setResultsPerPage(int resultsPerPage) {
this.resultsPerPage = resultsPerPage;
}
public Map getVideosResults() {
return videosResults;
}
public void setVideosResults(Map videosResults) {
this.videosResults = videosResults;
}
public void setContentDetailsVideos(String contentDetailsJson) {
JSONObject jo = new JSONObject(contentDetailsJson);
if (jo.has("items")) {
JSONArray items = jo.getJSONArray("items");
for (int i = 0; i < items.length(); i++) {
JSONObject item = items.getJSONObject(i);
VideoYoutube video = videosResults.get(item.get("id"));
JSONObject cd = item.getJSONObject("contentDetails");
video.setDuration(cd.getString("duration"));
JSONObject stats = item.getJSONObject("statistics");
video.setViewCount(stats.getLong("viewCount"));
videosResults.put(video.getVideoId(), video);
}
}
}
}
3. Servicio spring que contendrá la api necesaria para la búsqueda de videos.
/**
*
* @author Williams Rivas Created 20/02/2014 12:59:49
*
*/
@Service
public class YoutubeServiceImpl implements YoutubeService {
public static final String PARAM_MAX_RESULTS = "maxResults";
private static int DEFAULT_VALUE_MAX_RESULTS = 16;
private static String DEFAULT_VALUE_CHANNEL_ID = “XXXXXXXXXXXXXXXXXXXXXX";
public static final String PARAM_PART = "part";
public static final String DEFAULT_VALUE_PART = "snippet";
public static final String DEFAULT_VALUE_PARTS_VIDEOS = "contentDetails%2Cstatistics";
public static final String PARAM_PAGE_TOKEN = "pageToken";
public static final String PARAM_CHANNEL_ID = "channelId";
public static final String PARAM_VIDEO_IDS = "id";
public static final String PARAM_KEY = "key";
private static final String DEFAULT_VALUE_KEY = "API_KEY_GENERADA_EN_EL_PASO_UNO_DEL_CONTENIDO_DE_ESTE_POST";
public static final String URL_SEARCH_YOUTUBE = "https://www.googleapis.com/youtube/v3/search?type=video&";
public static final String URL_VIDEOS_LIST = "https://www.googleapis.com/youtube/v3/videos?";
public static final String PARAM_VALUE_SEPARATOR = "=";
public static final String PARAM_VALUE_CONCAT = "&";
public static final String PARAM_VALUE_COMMA = "%2C";
@Autowired
protected YoutubeCanalRepository youtubeCanalRepository;
@Override
public PageResultYoutube searchYoutubeVideos(String part, String channelId,
int maxResults, String pageToken) throws SearchYoutubeException {
setDefaultValues();
String query = buildQueryUrl(part, channelId, maxResults, pageToken,
null);
String dataJson = Util.httpGet(query);
PageResultYoutube page = new PageResultYoutube(dataJson);
String videoIds = page.getVideoIdsSeparetedByComma();
String contentDetailsJson = "";
try {
contentDetailsJson = Util.httpGet(buildQueryUrl(
DEFAULT_VALUE_PARTS_VIDEOS, null, DEFAULT_VALUE_MAX_RESULTS,
null, videoIds));
} catch (HttpGetException e) {
throw new SearchYoutubeException(e.getMessage());
}
page.setContentDetailsVideos(contentDetailsJson);
return page;
}
@Override
public PageResultYoutube searchYoutubeVideos(String part, String channelId,
int maxResults) throws SearchYoutubeException {
return searchYoutubeVideos(part, channelId, maxResults, null);
}
@Override
public PageResultYoutube searchYoutubeVideos()
throws SearchYoutubeException {
return searchYoutubeVideos(DEFAULT_VALUE_PART,
DEFAULT_VALUE_CHANNEL_ID, DEFAULT_VALUE_MAX_RESULTS);
}
private String buildQueryUrl(String part, String channelId, int maxResults,
String pageToken, String videoIds) {
StringBuilder url;
if (channelId != null && !channelId.equals("")) {
url = new StringBuilder(URL_SEARCH_YOUTUBE);
// parametro part
url.append(PARAM_PART).append(PARAM_VALUE_SEPARATOR);
if (part != null && !part.equals("")) {
if (!part.equals(DEFAULT_VALUE_PART)) {
url.append(DEFAULT_VALUE_PART);
} else {
url.append(part);
}
} else {
url.append(DEFAULT_VALUE_PART);
}
// parametro channel id
url.append(PARAM_VALUE_CONCAT).append(PARAM_CHANNEL_ID)
.append(PARAM_VALUE_SEPARATOR);
if (channelId != null && !channelId.equals("")) {
url.append(channelId);
} else {
url.append(DEFAULT_VALUE_CHANNEL_ID);
}
// pagina
if (pageToken != null && !pageToken.equals("")) {
url.append(PARAM_VALUE_CONCAT).append(PARAM_PAGE_TOKEN)
.append(PARAM_VALUE_SEPARATOR).append(pageToken);
}
} else { // busqueda no por canal sino por ids de videos
// https://www.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&id=uWKM4F2RAtI%2CJ2NIttHwZBA%2C6UliPT1LIc4%2CSaLWwDyHMvo&maxResults=3&key=AIzaSyBB8x12DXzrzXKhkum5f_Nv3Yl7-0GSwCg
url = new StringBuilder(URL_VIDEOS_LIST);
// parametro part
url.append(PARAM_PART).append(PARAM_VALUE_SEPARATOR);
if (part != null && !part.equals("")) {
if (!part.equals(DEFAULT_VALUE_PARTS_VIDEOS)) {
url.append(DEFAULT_VALUE_PARTS_VIDEOS);
} else {
url.append(part);
}
} else {
url.append(DEFAULT_VALUE_PARTS_VIDEOS);
}
// videos ids separados por comma
if (videoIds != null && !videoIds.equals("")) {
url.append(PARAM_VALUE_CONCAT).append(PARAM_VIDEO_IDS)
.append(PARAM_VALUE_SEPARATOR);
url.append(videoIds);
}
}
// max results
url.append(PARAM_VALUE_CONCAT).append(PARAM_MAX_RESULTS)
.append(PARAM_VALUE_SEPARATOR);
if (maxResults <= 0) {
url.append(DEFAULT_VALUE_MAX_RESULTS);
} else {
url.append(maxResults);
}
// key developer
url.append(PARAM_VALUE_CONCAT).append(PARAM_KEY)
.append(PARAM_VALUE_SEPARATOR).append(DEFAULT_VALUE_KEY);
return url.toString();
}
@Override
public PageResultYoutube searchYoutubeVideos(String pageToken)
throws SearchYoutubeException {
return searchYoutubeVideos(DEFAULT_VALUE_PART,
DEFAULT_VALUE_CHANNEL_ID, DEFAULT_VALUE_MAX_RESULTS, pageToken);
}
private void setDefaultValues() {
List canals = youtubeCanalRepository.findAll();
if (canals != null && canals.size() > 0) {
DEFAULT_VALUE_CHANNEL_ID = canals.get(0).getIdChannel();
DEFAULT_VALUE_MAX_RESULTS = canals.get(0).getMaxResults();
}
}
}
4. Definición del controlador que recibirá las peticiones realizadas por el usuario.
@RequestMapping("/youtube/**")
@Controller
public class YoutubeController extends GlobalModelAttributes {
@Autowired
private YoutubeService youtube;
@RequestMapping(method = RequestMethod.POST, value = "{id}")
public void post(@PathVariable Long id, ModelMap modelMap,
HttpServletRequest request, HttpServletResponse response) {
}
@RequestMapping
public String index(
@RequestParam(value = "page", required = false) String page,
ModelMap uiModel) {
PageResultYoutube result;
if (page != null) {
result = youtube.searchYoutubeVideos(page);
} else {
result = youtube.searchYoutubeVideos();
}
uiModel.addAttribute("youtube", result);
return "youtube/index";
}
}
5. Diseño de la vista para mostrar la lista de videos de manera paginada.
Videos Youtube
${entry.value.title}
${entry.value.duration}
${entry.value.viewCount}
6. Vista de la aplicación