at.aau.itec.android.mediaplayer.dash.DashParser.java Source code

Java tutorial

Introduction

Here is the source code for at.aau.itec.android.mediaplayer.dash.DashParser.java

Source

/*
 * Copyright (c) 2014 Mario Guggenberger <mg@itec.aau.at>
 *
 * This file is part of ITEC MediaPlayer.
 *
 * ITEC MediaPlayer 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.
 *
 * ITEC MediaPlayer 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 ITEC MediaPlayer.  If not, see <http://www.gnu.org/licenses/>.
 */

package at.aau.itec.android.mediaplayer.dash;

import android.util.Log;
import android.util.Xml;

import com.squareup.okhttp.Headers;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import at.aau.itec.android.mediaplayer.UriSource;

/**
 * Created by maguggen on 27.08.2014.
 */
public class DashParser {

    private static final String TAG = DashParser.class.getSimpleName();

    private static Pattern PATTERN_TIME = Pattern.compile("PT((\\d+)H)?((\\d+)M)?((\\d+(\\.\\d+)?)S)");

    /**
     * Parses an MPD XML file. This needs to be executed off the main thread, else a
     * NetworkOnMainThreadException gets thrown.
     * @param source the URl of an MPD XML file
     * @return a MPD object
     * @throws android.os.NetworkOnMainThreadException if executed on the main thread
     */
    public MPD parse(UriSource source) {
        MPD mpd = null;
        OkHttpClient httpClient = new OkHttpClient();

        Headers.Builder headers = new Headers.Builder();
        if (source.getHeaders() != null && !source.getHeaders().isEmpty()) {
            for (String name : source.getHeaders().keySet()) {
                headers.add(name, source.getHeaders().get(name));
            }
        }

        Request.Builder request = new Request.Builder().url(source.getUri().toString()).headers(headers.build());

        try {
            Response response = httpClient.newCall(request.build()).execute();
            if (!response.isSuccessful()) {
                throw new IOException("error requesting the MPD");
            }
            mpd = parse(response.body().byteStream());
        } catch (IOException e) {
            Log.e(TAG, "error downloading the MPD", e);
            throw new RuntimeException("error downloading the MPD", e);
        } catch (XmlPullParserException e) {
            Log.e(TAG, "error parsing the MPD", e);
            throw new RuntimeException("error parsing the MPD", e);
        }

        return mpd;
    }

    private MPD parse(InputStream in) throws XmlPullParserException, IOException {
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            parser.setInput(in, null);

            String baseUrl = "";
            MPD mpd = new MPD();

            int type = 0;
            while ((type = parser.nextTag()) >= 0) {
                if (type == XmlPullParser.START_TAG) {
                    String tagName = parser.getName();

                    if (tagName.equals("MPD")) {
                        mpd.mediaPresentationDurationUs = getAttributeValueTime(parser,
                                "mediaPresentationDuration");
                        mpd.minBufferTimeUs = getAttributeValueTime(parser, "minBufferTime");
                    } else if (tagName.equals("BaseURL")) {
                        baseUrl = parser.nextText();
                        Log.d(TAG, "base url: " + baseUrl);
                    } else if (tagName.equals("AdaptationSet")) {
                        mpd.adaptationSets.add(readAdaptationSet(mpd, baseUrl, parser));
                    }
                } else if (type == XmlPullParser.END_TAG) {
                    String tagName = parser.getName();
                    if (tagName.equals("MPD")) {
                        break;
                    }
                }
            }

            Log.d(TAG, mpd.toString());

            return mpd;
        } finally {
            in.close();
        }
    }

    private AdaptationSet readAdaptationSet(MPD mpd, String baseUrl, XmlPullParser parser)
            throws XmlPullParserException, IOException {
        AdaptationSet adaptationSet = new AdaptationSet();

        adaptationSet.group = getAttributeValueInt(parser, "group");
        adaptationSet.mimeType = getAttributeValue(parser, "mimeType");

        int type = 0;
        while ((type = parser.nextTag()) >= 0) {
            if (type == XmlPullParser.START_TAG) {
                String tagName = parser.getName();

                if (tagName.equals("Representation")) {
                    adaptationSet.representations.add(readRepresentation(mpd, adaptationSet, baseUrl, parser));
                }
            } else if (type == XmlPullParser.END_TAG) {
                String tagName = parser.getName();
                if (tagName.equals("AdaptationSet")) {
                    return adaptationSet;
                }
            }
        }

        throw new RuntimeException("invalid state");
    }

    private Representation readRepresentation(MPD mpd, AdaptationSet adaptationSet, String baseUrl,
            XmlPullParser parser) throws XmlPullParserException, IOException {
        Representation representation = new Representation();

        representation.id = getAttributeValue(parser, "id");
        representation.codec = getAttributeValue(parser, "codecs");
        representation.mimeType = getAttributeValue(parser, "mimeType", adaptationSet.mimeType);
        if (representation.mimeType.startsWith("video/")) {
            representation.width = getAttributeValueInt(parser, "width");
            representation.height = getAttributeValueInt(parser, "height");
        }
        representation.bandwidth = getAttributeValueInt(parser, "bandwidth");

        Log.d(TAG, representation.toString());

        int type = 0;
        while ((type = parser.nextTag()) >= 0) {
            String tagName = parser.getName();

            if (type == XmlPullParser.START_TAG) {
                if (tagName.equals("Initialization")) {
                    representation.initSegment = new Segment(baseUrl + getAttributeValue(parser, "sourceURL"),
                            getAttributeValue(parser, "range"));
                    Log.d(TAG, "Initialization: " + representation.initSegment.toString());
                } else if (tagName.equals("SegmentList")) {
                    long timescale = getAttributeValueLong(parser, "timescale", 1);
                    long duration = getAttributeValueLong(parser, "duration");
                    representation.segmentDurationUs = (long) (((double) duration / timescale) * 1000000d);
                } else if (tagName.equals("SegmentURL")) {
                    representation.segments.add(new Segment(baseUrl + getAttributeValue(parser, "media"),
                            getAttributeValue(parser, "mediaRange")));
                } else if (tagName.equals("SegmentTemplate")) {
                    long timescale = getAttributeValueLong(parser, "timescale", 1);
                    long duration = getAttributeValueLong(parser, "duration");
                    representation.segmentDurationUs = (long) (((double) duration / timescale) * 1000000d);
                    int startNumber = getAttributeValueInt(parser, "startNumber");
                    int numSegments = (int) Math
                            .ceil((double) mpd.mediaPresentationDurationUs / representation.segmentDurationUs);

                    // init segments
                    representation.initSegment = new Segment(baseUrl + getAttributeValue(parser, "initialization"));

                    // media segments
                    String mediaUrl = getAttributeValue(parser, "media");
                    for (int i = startNumber; i < startNumber + numSegments; i++) {
                        representation.segments.add(new Segment(baseUrl + mediaUrl.replace("$Number$", i + "")));
                    }
                } else if (tagName.equals("BaseURL")) {
                    String newBaseUrl = parser.nextText();
                    Log.d(TAG, "new base url: " + newBaseUrl);
                }
            } else if (type == XmlPullParser.END_TAG) {
                if (tagName.equals("Representation")) {
                    return representation;
                }
            }
        }

        throw new RuntimeException("invalid state");
    }

    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            throw new IllegalStateException();
        }
        int depth = 1;
        while (depth != 0) {
            switch (parser.next()) {
            case XmlPullParser.END_TAG:
                depth--;
                break;
            case XmlPullParser.START_TAG:
                depth++;
                break;
            }
        }
    }

    /**
     * Parse a timestamp and return its duration in microseconds.
     * http://en.wikipedia.org/wiki/ISO_8601#Durations
     */
    private static long parseTime(String time) {
        Matcher matcher = PATTERN_TIME.matcher(time);

        if (matcher.matches()) {
            long hours = 0;
            long minutes = 0;
            double seconds = 0;

            String group = matcher.group(2);
            if (group != null) {
                hours = Long.parseLong(group);
            }
            group = matcher.group(4);
            if (group != null) {
                minutes = Long.parseLong(group);
            }
            group = matcher.group(6);
            if (group != null) {
                seconds = Double.parseDouble(group);
            }

            return (long) (seconds * 1000 * 1000) + minutes * 60 * 1000 * 1000 + hours * 60 * 60 * 1000 * 1000;
        }

        return -1;
    }

    private static String getAttributeValue(XmlPullParser parser, String name, String defValue) {
        String value = parser.getAttributeValue(null, name);
        return value != null ? value : defValue;
    }

    private static String getAttributeValue(XmlPullParser parser, String name) {
        return getAttributeValue(parser, name, null);
    }

    private static int getAttributeValueInt(XmlPullParser parser, String name) {
        return Integer.parseInt(getAttributeValue(parser, name, "0"));
    }

    private static long getAttributeValueLong(XmlPullParser parser, String name) {
        return Long.parseLong(getAttributeValue(parser, name, "0"));
    }

    private static long getAttributeValueLong(XmlPullParser parser, String name, long defValue) {
        return Long.parseLong(getAttributeValue(parser, name, defValue + ""));
    }

    private static long getAttributeValueTime(XmlPullParser parser, String name) {
        return parseTime(getAttributeValue(parser, name));
    }
}