All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.iheartradio.m3u8.MediaPlaylistLineParser Maven / Gradle / Ivy

There is a newer version: 0.2.6
Show newest version
package com.iheartradio.m3u8;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import com.iheartradio.m3u8.data.EncryptionData;
import com.iheartradio.m3u8.data.EncryptionData.Builder;
import com.iheartradio.m3u8.data.EncryptionMethod;
import com.iheartradio.m3u8.data.PlaylistType;
import com.iheartradio.m3u8.data.StartData;
import com.iheartradio.m3u8.data.TrackInfo;

class MediaPlaylistLineParser implements LineParser {
    private final IExtTagParser tagParser;
    private final LineParser lineParser;

    MediaPlaylistLineParser(IExtTagParser parser) {
        this(parser, new ExtLineParser(parser));
    }

    MediaPlaylistLineParser(IExtTagParser tagParser, LineParser lineParser) {
        this.tagParser = tagParser;
        this.lineParser = lineParser;
    }

    @Override
    public void parse(String line, ParseState state) throws ParseException {
        if (state.isMaster()) {
            throw ParseException.create(ParseExceptionType.MEDIA_IN_MASTER, tagParser.getTag());
        }

        state.setMedia();
        lineParser.parse(line, state);
    }

    // media playlist tags
    
    static final IExtTagParser EXT_X_ENDLIST = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_ENDLIST_TAG;
        }

        @Override
        public boolean hasData() {
            return false;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            ParseUtil.match(Constants.EXT_X_ENDLIST_PATTERN, line, getTag());
            state.getMedia().endOfList = true;
        }
    };
    
    static final IExtTagParser EXT_X_I_FRAMES_ONLY = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_I_FRAMES_ONLY_TAG;
        }

        @Override
        public boolean hasData() {
            return false;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);
            
            ParseUtil.match(Constants.EXT_X_I_FRAMES_ONLY_PATTERN, line, getTag());
            
            if (state.getCompatibilityVersion() < 4) {
                throw ParseException.create(ParseExceptionType.REQUIRES_PROTOCOL_VERSION_4_OR_HIGHER, getTag());
            }
            
            state.setIsIframesOnly();
        }
    };
    
    static final IExtTagParser EXT_X_PLAYLIST_TYPE = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_PLAYLIST_TYPE_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }
        
        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final Matcher matcher = ParseUtil.match(Constants.EXT_X_PLAYLIST_TYPE_PATTERN, line, getTag());

            if (state.getMedia().playlistType != null) {
                throw ParseException.create(ParseExceptionType.MULTIPLE_EXT_TAG_INSTANCES, getTag(), line);
            }

            state.getMedia().playlistType = ParseUtil.parseEnum(matcher.group(1), PlaylistType.class, getTag());
        }
    };
    
    static final IExtTagParser EXT_X_START = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);
        private final Map> HANDLERS = new HashMap<>();
        
        {
            HANDLERS.put(Constants.TIME_OFFSET, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, StartData.Builder builder, ParseState state) throws ParseException {
                    builder.withTimeOffset(ParseUtil.parseFloat(attribute.value, getTag()));
                }
            });
            
            HANDLERS.put(Constants.PRECISE, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, StartData.Builder builder, ParseState state) throws ParseException {
                    builder.withPrecise(ParseUtil.parseYesNo(attribute, getTag()));
                }
            });
        }

        @Override
        public String getTag() {
            return Constants.EXT_X_START_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }
        
        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final StartData.Builder builder = new StartData.Builder();
            ParseUtil.parseAttributes(line, builder, state, HANDLERS, getTag());
            final StartData startData = builder.build();

            state.getMedia().startData = startData;
        }
    };
    
    static final IExtTagParser EXT_X_TARGETDURATION = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_TARGETDURATION_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final Matcher matcher = ParseUtil.match(Constants.EXT_X_TARGETDURATION_PATTERN, line, getTag());

            if (state.getMedia().targetDuration != null) {
                throw ParseException.create(ParseExceptionType.MULTIPLE_EXT_TAG_INSTANCES, getTag(), line);
            }

            state.getMedia().targetDuration = ParseUtil.parseInt(matcher.group(1), getTag());
        }
    };

    static final IExtTagParser EXT_X_MEDIA_SEQUENCE = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_MEDIA_SEQUENCE_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final Matcher matcher = ParseUtil.match(Constants.EXT_X_MEDIA_SEQUENCE_PATTERN, line, getTag());

            if (state.getMedia().mediaSequenceNumber != null) {
                throw ParseException.create(ParseExceptionType.MULTIPLE_EXT_TAG_INSTANCES, getTag(), line);
            }

            state.getMedia().mediaSequenceNumber = ParseUtil.parseInt(matcher.group(1), getTag());
        }
    };

    static final IExtTagParser EXT_X_ALLOW_CACHE = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXT_X_ALLOW_CACHE_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            // deprecated
        }
    };

    // media segment tags

    static final IExtTagParser EXTINF = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);

        @Override
        public String getTag() {
            return Constants.EXTINF_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }

        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final Matcher matcher = ParseUtil.match(Constants.EXTINF_PATTERN, line, getTag());

            state.getMedia().trackInfo = new TrackInfo(ParseUtil.parseFloat(matcher.group(1), getTag()), matcher.group(2));
        }
    };

    static final IExtTagParser EXT_X_KEY = new IExtTagParser() {
        private final LineParser lineParser = new MediaPlaylistLineParser(this);
        private final Map> HANDLERS = new HashMap<>();

        {
            HANDLERS.put(Constants.METHOD, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException {
                    final EncryptionMethod method = EncryptionMethod.fromValue(attribute.value);

                    if (method == null) {
                        throw ParseException.create(ParseExceptionType.INVALID_ENCRYPTION_METHOD, getTag(), attribute.toString());
                    } else {
                        builder.withMethod(method);
                    }
                }
            });

            HANDLERS.put(Constants.URI, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException {
                    builder.withUri(ParseUtil.decodeUri(ParseUtil.parseQuotedString(attribute.value, getTag()), state.encoding));
                }
            });

            HANDLERS.put(Constants.IV, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException {
                    final List initializationVector = ParseUtil.parseHexadecimal(attribute.value, getTag());

                    if ((initializationVector.size() != Constants.IV_SIZE) && 
                        (initializationVector.size() != Constants.IV_SIZE_ALTERNATIVE)) {
                        throw ParseException.create(ParseExceptionType.INVALID_IV_SIZE, getTag(), attribute.toString());
                    }

                    builder.withInitializationVector(initializationVector);
                }
            });

            HANDLERS.put(Constants.KEY_FORMAT, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException {
                    builder.withKeyFormat(ParseUtil.parseQuotedString(attribute.value, getTag()));
                }
            });

            HANDLERS.put(Constants.KEY_FORMAT_VERSIONS, new AttributeParser() {
                @Override
                public void parse(Attribute attribute, Builder builder, ParseState state) throws ParseException {
                    final String[] versionStrings = ParseUtil.parseQuotedString(attribute.value, getTag()).split(Constants.LIST_SEPARATOR);
                    final List versions = new ArrayList<>();

                    for (String version : versionStrings) {
                        try {
                            versions.add(Integer.parseInt(version));
                        } catch (NumberFormatException exception) {
                            throw ParseException.create(ParseExceptionType.INVALID_KEY_FORMAT_VERSIONS, getTag(), attribute.toString());
                        }
                    }
                    
                    builder.withKeyFormatVersions(versions);
                }
            });
        }

        @Override
        public String getTag() {
            return Constants.EXT_X_KEY_TAG;
        }

        @Override
        public boolean hasData() {
            return true;
        }
        
        @Override
        public void parse(String line, ParseState state) throws ParseException {
            lineParser.parse(line, state);

            final EncryptionData.Builder builder = new EncryptionData.Builder()
                    .withKeyFormat(Constants.DEFAULT_KEY_FORMAT)
                    .withKeyFormatVersions(Constants.DEFAULT_KEY_FORMAT_VERSIONS);

            ParseUtil.parseAttributes(line, builder, state, HANDLERS, getTag());

            final EncryptionData encryptionData = builder.build();

            if (encryptionData.getMethod() != EncryptionMethod.NONE && encryptionData.getUri() == null) {
                throw ParseException.create(ParseExceptionType.MISSING_ENCRYPTION_URI, getTag(), line);
            }

            state.getMedia().encryptionData = encryptionData;
        }
    };
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy