/* This file is part of Airsonic. Airsonic 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. Airsonic 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 Airsonic. If not, see . Copyright 2016 (C) Airsonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ package org.airsonic.player.io; import org.airsonic.player.util.HttpRange; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; /** * Special output stream for grabbing only part of a passed stream. * * @author Sindre Mehus */ public class RangeOutputStream extends FilterOutputStream { private final HttpRange range; /** * The current position. */ protected long pos; public RangeOutputStream(OutputStream out, HttpRange range) { super(out); this.range = range; } /** * Wraps the given output stream in a RangeOutputStream, using the values * in the given range, unless the range is null in which case * the original OutputStream is returned. * * @param out The output stream to wrap in a RangeOutputStream. * @param range The range, may be null. * @return The possibly wrapped output stream. */ public static OutputStream wrap(OutputStream out, HttpRange range) { // Skip filtering if there's no range or it represents the entire stream. if (range == null || (range.getFirstBytePos() == 0 && !range.isClosed())) { return out; } return new RangeOutputStream(out, range); } /** * Writes the byte if it's within the range. * * @param b The byte to write. * @throws IOException Thrown if there was a problem writing to the stream. */ @Override public void write(int b) throws IOException { if (range.contains(pos)) { super.write(b); } pos++; } /** * Writes the subset of the bytes that are within the range. * * @param b The bytes to write. * @param off The offset to start at. * @param len The number of bytes to write. * @throws IOException Thrown if there was a problem writing to the stream. */ @Override public void write(byte[] b, int off, int len) throws IOException { long start = range.getFirstBytePos(); Long end = range.getLastBytePos(); if (pos + len >= start && (end == null || pos <= end)) { long skipStart = Math.max(0, start - pos); long newOff = off + skipStart; long newLen = len - skipStart; if (end != null) { newLen = min(newLen, end - pos + 1, end - start + 1); } out.write(b, (int) newOff, (int) newLen); } pos += len; } private long min(long a, long b, long c) { return Math.min(a, Math.min(b, c)); } }