/* * Copyright (c) Christopher A Longo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.chrislongo.hls; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class PlaylistDownloader { private URL url; private List<String> playlist; private Crypto crypto; private static String EXT_X_KEY = "#EXT-X-KEY"; private static final String BANDWIDTH = "BANDWIDTH"; public PlaylistDownloader(String playlistUrl) throws MalformedURLException { this.url = new URL(playlistUrl); this.playlist = new ArrayList<String>(); } public void download(String outfile) throws IOException { this.download(outfile, null); } public void download(String outfile, String key) throws IOException { fetchPlaylist(); this.crypto = new Crypto(getBaseUrl(this.url), key); for (String line : playlist) { line = line.trim(); if (line.startsWith(EXT_X_KEY)) { crypto.updateKeyString(line); System.out.printf("\rCurrent Key: %s \n", crypto.getCurrentKey()); System.out.printf("Current IV: %s\n", crypto.getCurrentIV()); } else if (line.length() > 0 && !line.startsWith("#")) { URL segmentUrl; if (!line.startsWith("http")) { String baseUrl = getBaseUrl(this.url); segmentUrl = new URL(baseUrl + line); } else { segmentUrl = new URL(line); } downloadInternal(segmentUrl, outfile); } } System.out.println("\nDone."); } private void downloadInternal(URL segmentUrl, String outFile) throws IOException { byte[] buffer = new byte[512]; InputStream is = crypto.hasKey() ? crypto.wrapInputStream(segmentUrl.openStream()) : segmentUrl.openStream(); FileOutputStream out; if (outFile != null) { File file = new File(outFile); out = new FileOutputStream(outFile, file.exists()); } else { String path = segmentUrl.getPath(); int pos = path.lastIndexOf('/'); out = new FileOutputStream(path.substring(++pos), false); } System.out.printf("Downloading segment: %s\r", segmentUrl); int read; while ((read = is.read(buffer)) >= 0) { out.write(buffer, 0, read); } is.close(); out.close(); } private String getBaseUrl(URL url) { String urlString = url.toString(); int index = urlString.lastIndexOf('/'); return urlString.substring(0, ++index); } private void fetchPlaylist() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); boolean isMaster = false; long maxRate = 0L; int maxRateIndex = 0; String line; int index = 0; while ((line = reader.readLine()) != null) { playlist.add(line); if (line.contains(BANDWIDTH)) isMaster = true; if (isMaster && line.contains(BANDWIDTH)) { try { int pos = line.lastIndexOf("="); long bandwidth = Long.parseLong(line.substring(++pos)); maxRate = Math.max(bandwidth, maxRate); if (bandwidth == maxRate) maxRateIndex = index + 1; } catch (NumberFormatException ignore) {} } index++; } reader.close(); if (isMaster) { System.out.printf("Found master playlist, fetching highest stream at %dKb/s\n", maxRate / 1024); this.url = updateUrlForSubPlaylist(playlist.get(maxRateIndex)); this.playlist.clear(); fetchPlaylist(); } } private URL updateUrlForSubPlaylist(String sub) throws MalformedURLException { String newUrl; if (!sub.startsWith("http")) { newUrl = getBaseUrl(this.url) + sub; } else { newUrl = sub; } return new URL(newUrl); } }