diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings
index 57da8554c..0ee26c103 100644
--- a/Telegram/Resources/lang.strings
+++ b/Telegram/Resources/lang.strings
@@ -638,6 +638,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 "lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment. {more_info}";
 "lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment. {more_info}";
 "lng_cant_more_info" = "More info ยป";
+"lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of the privacy settings.";
+"lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of the privacy settings.";
 
 "lng_send_button" = "Send";
 "lng_message_ph" = "Write a message..";
diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt
index c52dfd796..98b23e2db 100644
--- a/Telegram/Resources/style.txt
+++ b/Telegram/Resources/style.txt
@@ -1286,6 +1286,19 @@ msgFileRadialLine: 3px;
 
 msgVideoSize: size(320px, 240px);
 
+msgWaveformBar: 2px;
+msgWaveformSkip: 1px;
+msgWaveformMin: 2px;
+msgWaveformMax: 20px;
+msgWaveformInActive: #59b6eb;
+msgWaveformInActiveSelected: #51a3d3;
+msgWaveformInInactive: #d4dee6;
+msgWaveformInInactiveSelected: #9cc1e1;
+msgWaveformOutActive: #78c67f;
+msgWaveformOutActiveSelected: #6badad;
+msgWaveformOutInactive: #b3e2b4;
+msgWaveformOutInactiveSelected: #91c3c3;
+
 sendPadding: 9px;
 btnSend: flatButton(btnDefFlat) {
 	color: btnYesColor;
@@ -1386,7 +1399,7 @@ btnRecordAudio: sprite(379px, 390px, 16px, 24px);
 btnRecordAudioActive: sprite(379px, 366px, 16px, 24px);
 recordSignalColor: #f17077;
 recordSignalMin: 5px;
-recordSignalMax: 10px;
+recordSignalMax: 12px;
 recordCancel: #aaa;
 recordCancelActive: #ec6466;
 recordFont: font(13px);
diff --git a/Telegram/SourceFiles/_other/updater.cpp b/Telegram/SourceFiles/_other/updater.cpp
index 9dea213aa..d89efcd95 100644
--- a/Telegram/SourceFiles/_other/updater.cpp
+++ b/Telegram/SourceFiles/_other/updater.cpp
@@ -482,13 +482,18 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
 	static const int maxFileLen = MAX_PATH * 10;
 
 	WCHAR szPath[maxFileLen];
-	wsprintf(szPath, L"%stdumps\\", path);
-
+	wsprintf(szPath, L"%stdata\\", path);
     if (!CreateDirectory(szPath, NULL)) {
 		if (GetLastError() != ERROR_ALREADY_EXISTS) {
 			return 0;
 		}
 	}
+	wsprintf(szPath, L"%sdumps\\", path);
+	if (!CreateDirectory(szPath, NULL)) {
+		if (GetLastError() != ERROR_ALREADY_EXISTS) {
+			return 0;
+		}
+	}
 
     WCHAR szFileName[maxFileLen];
 	WCHAR szExeName[maxFileLen];
diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp
index 9002e8aed..67ad44426 100644
--- a/Telegram/SourceFiles/app.cpp
+++ b/Telegram/SourceFiles/app.cpp
@@ -47,8 +47,6 @@ namespace {
 	UpdatedPeers updatedPeers;
 
 	PhotosData photosData;
-	VideosData videosData;
-	AudiosData audiosData;
 	DocumentsData documentsData;
 
 	typedef QHash<QString, ImageLinkData*> ImageLinksData;
@@ -64,8 +62,6 @@ namespace {
 	ChannelReplyMarkups channelReplyMarkups;
 
 	PhotoItems photoItems;
-	VideoItems videoItems;
-	AudioItems audioItems;
 	DocumentItems documentItems;
 	WebPageItems webPageItems;
 	SharedContactItems sharedContactItems;
@@ -1289,26 +1285,6 @@ namespace App {
 		return App::photoSet(photo.vid.v, convert, 0, 0, ImagePtr(), ImagePtr(), ImagePtr());
 	}
 
-	VideoData *feedVideo(const MTPDvideo &video, VideoData *convert) {
-		return App::videoSet(video.vid.v, convert, video.vaccess_hash.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v);
-	}
-
-	AudioData *feedAudio(const MTPaudio &audio, AudioData *convert) {
-		switch (audio.type()) {
-		case mtpc_audio: {
-			return feedAudio(audio.c_audio(), convert);
-		} break;
-		case mtpc_audioEmpty: {
-			return App::audioSet(audio.c_audioEmpty().vid.v, convert, 0, 0, QString(), 0, 0, 0);
-		} break;
-		}
-		return App::audio(0);
-	}
-
-	AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert) {
-		return App::audioSet(audio.vid.v, convert, audio.vaccess_hash.v, audio.vdate.v, qs(audio.vmime_type), audio.vduration.v, audio.vdc_id.v, audio.vsize.v);
-	}
-
 	DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb) {
 		switch (document.type()) {
 		case mtpc_document: {
@@ -1514,110 +1490,6 @@ namespace App {
 		return result;
 	}
 
-	VideoData *video(const VideoId &video) {
-		VideosData::const_iterator i = ::videosData.constFind(video);
-		if (i == ::videosData.cend()) {
-			i = ::videosData.insert(video, new VideoData(video));
-		}
-		return i.value();
-	}
-
-	VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) {
-		if (convert) {
-			if (convert->id != video) {
-				VideosData::iterator i = ::videosData.find(convert->id);
-				if (i != ::videosData.cend() && i.value() == convert) {
-					::videosData.erase(i);
-				}
-				convert->id = video;
-				convert->status = FileReady;
-			}
-			if (date) {
-				convert->access = access;
-				convert->date = date;
-				updateImage(convert->thumb, thumb);
-				convert->duration = duration;
-				convert->w = w;
-				convert->h = h;
-				convert->dc = dc;
-				convert->size = size;
-			}
-		}
-		VideosData::const_iterator i = ::videosData.constFind(video);
-		VideoData *result;
-		if (i == ::videosData.cend()) {
-			if (convert) {
-				result = convert;
-			} else {
-				result = new VideoData(video, access, date, duration, w, h, thumb, dc, size);
-			}
-			::videosData.insert(video, result);
-		} else {
-			result = i.value();
-			if (result != convert && date) {
-				result->access = access;
-				result->date = date;
-				result->duration = duration;
-				result->w = w;
-				result->h = h;
-				updateImage(result->thumb, thumb);
-				result->dc = dc;
-				result->size = size;
-			}
-		}
-		return result;
-	}
-
-	AudioData *audio(const AudioId &audio) {
-		AudiosData::const_iterator i = ::audiosData.constFind(audio);
-		if (i == ::audiosData.cend()) {
-			i = ::audiosData.insert(audio, new AudioData(audio));
-		}
-		return i.value();
-	}
-
-	AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) {
-		if (convert) {
-			if (convert->id != audio) {
-				AudiosData::iterator i = ::audiosData.find(convert->id);
-				if (i != ::audiosData.cend() && i.value() == convert) {
-					::audiosData.erase(i);
-				}
-				convert->id = audio;
-				convert->status = FileReady;
-			}
-			if (date) {
-				convert->access = access;
-				convert->date = date;
-				convert->mime = mime;
-				convert->duration = duration;
-				convert->dc = dc;
-				convert->size = size;
-			}
-		}
-		AudiosData::const_iterator i = ::audiosData.constFind(audio);
-		AudioData *result;
-		if (i == ::audiosData.cend()) {
-			if (convert) {
-				result = convert;
-			} else {
-				result = new AudioData(audio, access, date, mime, duration, dc, size);
-			}
-			::audiosData.insert(audio, result);
-		} else {
-			result = i.value();
-			if (result != convert && date) {
-				result->access = access;
-				result->date = date;
-				result->mime = mime;
-				result->duration = duration;
-				result->dc = dc;
-				result->size = size;
-			}
-		}
-		return result;
-	}
-
 	DocumentData *document(const DocumentId &document) {
 		DocumentsData::const_iterator i = ::documentsData.constFind(document);
 		if (i == ::documentsData.cend()) {
@@ -1634,10 +1506,15 @@ namespace App {
 				if (i != ::documentsData.cend() && i.value() == convert) {
 					::documentsData.erase(i);
 				}
-				Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document));
+
+				// inline bot sent gifs caching
+				if (!convert->voice() && !convert->isVideo()) {
+					Local::copyStickerImage(mediaKey(DocumentFileLocation, convert->dc, convert->id), mediaKey(DocumentFileLocation, dc, document));
+				}
+
 				convert->id = document;
 				convert->status = FileReady;
-				sentSticker = !!convert->sticker();
+				sentSticker = (convert->sticker() != 0);
 			}
 			if (date) {
 				convert->access = access;
@@ -1645,7 +1522,7 @@ namespace App {
 				convert->setattributes(attributes);
 				convert->mime = mime;
 				if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) {
-					convert->thumb = thumb;
+					updateImage(convert->thumb, thumb);
 				}
 				convert->dc = dc;
 				convert->size = size;
@@ -1661,7 +1538,7 @@ namespace App {
 
 			const FileLocation &loc(convert->location(true));
 			if (!loc.isEmpty()) {
-				Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), loc);
+				Local::writeFileLocation(convert->mediaKey(), loc);
 			}
 		}
 		DocumentsData::const_iterator i = ::documentsData.constFind(document);
@@ -1672,7 +1549,9 @@ namespace App {
 			} else {
 				result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size);
 				result->recountIsImage();
-				if (result->sticker()) result->sticker()->loc = thumbLocation;
+				if (result->sticker()) {
+					result->sticker()->loc = thumbLocation;
+				}
 			}
 			::documentsData.insert(document, result);
 		} else {
@@ -1693,7 +1572,9 @@ namespace App {
 				}
 			}
 		}
-		if (sentSticker && App::main()) App::main()->incrementSticker(result);
+		if (sentSticker && App::main()) {
+			App::main()->incrementSticker(result);
+		}
 		return result;
 	}
 
@@ -1792,12 +1673,6 @@ namespace App {
 		for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) {
 			i.value()->forget();
 		}
-		for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) {
-			i.value()->forget();
-		}
-		for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) {
-			i.value()->forget();
-		}
 		for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) {
 			i.value()->forget();
 		}
@@ -1951,14 +1826,6 @@ namespace App {
 			delete *i;
 		}
 		::photosData.clear();
-		for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) {
-			delete *i;
-		}
-		::videosData.clear();
-		for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) {
-			delete *i;
-		}
-		::audiosData.clear();
 		for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) {
 			delete *i;
 		}
@@ -1976,8 +1843,6 @@ namespace App {
 		cSetLastSavedGifsUpdate(0);
 		cSetReportSpamStatuses(ReportSpamStatuses());
 		::photoItems.clear();
-		::videoItems.clear();
-		::audioItems.clear();
 		::documentItems.clear();
 		::webPageItems.clear();
 		::sharedContactItems.clear();
@@ -2376,38 +2241,6 @@ namespace App {
 		return ::photosData;
 	}
 
-	void regVideoItem(VideoData *data, HistoryItem *item) {
-		::videoItems[data].insert(item, NullType());
-	}
-
-	void unregVideoItem(VideoData *data, HistoryItem *item) {
-		::videoItems[data].remove(item);
-	}
-
-	const VideoItems &videoItems() {
-		return ::videoItems;
-	}
-
-	const VideosData &videosData() {
-		return ::videosData;
-	}
-
-	void regAudioItem(AudioData *data, HistoryItem *item) {
-		::audioItems[data].insert(item, NullType());
-	}
-
-	void unregAudioItem(AudioData*data, HistoryItem *item) {
-		::audioItems[data].remove(item);
-	}
-
-	const AudioItems &audioItems() {
-		return ::audioItems;
-	}
-
-	const AudiosData &audiosData() {
-		return ::audiosData;
-	}
-
 	void regDocumentItem(DocumentData *data, HistoryItem *item) {
 		::documentItems[data].insert(item, NullType());
 	}
diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h
index 2b224e07c..760ea7780 100644
--- a/Telegram/SourceFiles/app.h
+++ b/Telegram/SourceFiles/app.h
@@ -36,16 +36,12 @@ class FileUploader;
 
 typedef QMap<HistoryItem*, NullType> HistoryItemsMap;
 typedef QHash<PhotoData*, HistoryItemsMap> PhotoItems;
-typedef QHash<VideoData*, HistoryItemsMap> VideoItems;
-typedef QHash<AudioData*, HistoryItemsMap> AudioItems;
 typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
 typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
 typedef QHash<int32, HistoryItemsMap> SharedContactItems;
 typedef QHash<ClipReader*, HistoryItem*> GifItems;
 
 typedef QHash<PhotoId, PhotoData*> PhotosData;
-typedef QHash<VideoId, VideoData*> VideosData;
-typedef QHash<AudioId, AudioData*> AudiosData;
 typedef QHash<DocumentId, DocumentData*> DocumentsData;
 
 struct ReplyMarkup {
@@ -106,9 +102,6 @@ namespace App {
 	PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs);
 	PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0);
 	PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0);
-	VideoData *feedVideo(const MTPDvideo &video, VideoData *convert = 0);
-	AudioData *feedAudio(const MTPaudio &audio, AudioData *convert = 0);
-	AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert = 0);
 	DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb);
 	DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0);
 	DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = 0);
@@ -136,10 +129,6 @@ namespace App {
 	QString peerName(const PeerData *peer, bool forDialogs = false);
 	PhotoData *photo(const PhotoId &photo);
 	PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full);
-	VideoData *video(const VideoId &video);
-	VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size);
-	AudioData *audio(const AudioId &audio);
-	AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size);
 	DocumentData *document(const DocumentId &document);
 	DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
 	WebPageData *webPage(const WebPageId &webPage);
@@ -214,16 +203,6 @@ namespace App {
 	const PhotoItems &photoItems();
 	const PhotosData &photosData();
 
-	void regVideoItem(VideoData *data, HistoryItem *item);
-	void unregVideoItem(VideoData *data, HistoryItem *item);
-	const VideoItems &videoItems();
-	const VideosData &videosData();
-
-	void regAudioItem(AudioData *data, HistoryItem *item);
-	void unregAudioItem(AudioData*data, HistoryItem *item);
-	const AudioItems &audioItems();
-	const AudiosData &audiosData();
-
 	void regDocumentItem(DocumentData *data, HistoryItem *item);
 	void unregDocumentItem(DocumentData *data, HistoryItem *item);
 	const DocumentItems &documentItems();
diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp
index 1f8c6a8b5..84520a5b7 100644
--- a/Telegram/SourceFiles/application.cpp
+++ b/Telegram/SourceFiles/application.cpp
@@ -1035,7 +1035,7 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) {
 	int32 filesize = 0;
 	QByteArray data;
 
-	ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, MTP_audioEmpty(MTP_long(0)), photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0);
+	ReadyLocalMedia ready(PreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, false, 0);
 
 	connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&, const MTPInputFile&)), App::app(), SLOT(photoUpdated(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection);
 
@@ -1048,9 +1048,9 @@ void AppClass::checkMapVersion() {
     if (Local::oldMapVersion() < AppVersion) {
 		if (Local::oldMapVersion()) {
 			QString versionFeatures;
-			if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9020) {
+			if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9022) {
 				if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
-					versionFeatures = QString::fromUtf8("\xe2\x80\x94 Testing new crash reporting system\n\xe2\x80\x94 Conversation history is centered in wide windows\n\xe2\x80\x94 New cute link and timestamp tooltips design\n\xe2\x80\x94 Bug fixes and other minor improvements");// .replace('@', qsl("@") + QChar(0x200D));
+					versionFeatures = QString::fromUtf8("\xe2\x80\x94 Voice messages waveform visualizations\n\xe2\x80\x94 Bug fixes and other minor improvements");// .replace('@', qsl("@") + QChar(0x200D));
 				} else {
 					versionFeatures = QString::fromUtf8("\xe2\x80\x94 Testing new crash reporting system\n\xe2\x80\x94 Conversation history is centered in wide windows\n\xe2\x80\x94 New cute link and timestamp tooltips design\n\xe2\x80\x94 Ctrl+W or Ctrl+F4 closes Telegram window\n\xe2\x80\x94 Bug fixes and other minor improvements");// .replace('@', qsl("@") + QChar(0x200D));
 				}
diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp
index db981b3aa..7771eb39e 100644
--- a/Telegram/SourceFiles/audio.cpp
+++ b/Telegram/SourceFiles/audio.cpp
@@ -96,6 +96,7 @@ bool _checkALError() {
 
 Q_DECLARE_METATYPE(AudioMsgId);
 Q_DECLARE_METATYPE(SongMsgId);
+Q_DECLARE_METATYPE(VoiceWaveform);
 void audioInit() {
 	if (!capture) {
 		capture = new AudioCapture();
@@ -206,6 +207,7 @@ void audioInit() {
 
 	qRegisterMetaType<AudioMsgId>();
 	qRegisterMetaType<SongMsgId>();
+	qRegisterMetaType<VoiceWaveform>();
 
 	player = new AudioPlayer();
 	alcDevicePauseSOFT(audioDevice);
@@ -368,8 +370,8 @@ void AudioPlayer::onStopped(const SongMsgId &song) {
 bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
 	Msg *data = 0;
 	switch (type) {
-	case OverviewAudios: data = &_audioData[_audioCurrent]; break;
-	case OverviewDocuments: data = &_songData[_songCurrent]; break;
+	case OverviewVoiceFiles: data = &_audioData[_audioCurrent]; break;
+	case OverviewFiles: data = &_songData[_songCurrent]; break;
 	}
 	if (!data) return false;
 
@@ -382,8 +384,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
 		if (!_checkALError()) {
 			setStoppedState(data, AudioPlayerStoppedAtError);
 			switch (type) {
-			case OverviewAudios: onError(_audioData[_audioCurrent].audio); break;
-			case OverviewDocuments: onError(_songData[_songCurrent].song); break;
+			case OverviewVoiceFiles: onError(_audioData[_audioCurrent].audio); break;
+			case OverviewFiles: onError(_songData[_songCurrent].song); break;
 			}
 			return false;
 		}
@@ -395,8 +397,8 @@ bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) {
 bool AudioPlayer::fadedStop(MediaOverviewType type, bool *fadedStart) {
 	Msg *current = 0;
 	switch (type) {
-	case OverviewAudios: current = &_audioData[_audioCurrent]; break;
-	case OverviewDocuments: current = &_songData[_songCurrent]; break;
+	case OverviewVoiceFiles: current = &_audioData[_audioCurrent]; break;
+	case OverviewFiles: current = &_songData[_songCurrent]; break;
 	}
 	if (!current) return false;
 
@@ -428,7 +430,7 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) {
 		bool fadedStart = false;
 		AudioMsg *current = &_audioData[_audioCurrent];
 		if (current->audio != audio) {
-			if (fadedStop(OverviewAudios, &fadedStart)) {
+			if (fadedStop(OverviewVoiceFiles, &fadedStart)) {
 				stopped = current->audio;
 			}
 			if (current->audio) {
@@ -472,7 +474,7 @@ void AudioPlayer::play(const SongMsgId &song, int64 position) {
 		bool fadedStart = false;
 		SongMsg *current = &_songData[_songCurrent];
 		if (current->song != song) {
-			if (fadedStop(OverviewDocuments, &fadedStart)) {
+			if (fadedStop(OverviewFiles, &fadedStart)) {
 				stopped = current->song;
 			}
 			if (current->song) {
@@ -513,11 +515,11 @@ bool AudioPlayer::checkCurrentALError(MediaOverviewType type) {
 	if (_checkALError()) return true;
 
 	switch (type) {
-	case OverviewAudios:
+	case OverviewVoiceFiles:
 		setStoppedState(&_audioData[_audioCurrent], AudioPlayerStoppedAtError);
 		onError(_audioData[_audioCurrent].audio);
 		break;
-	case OverviewDocuments:
+	case OverviewFiles:
 		setStoppedState(&_songData[_songCurrent], AudioPlayerStoppedAtError);
 		onError(_songData[_songCurrent].song);
 		break;
@@ -531,11 +533,11 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
 	Msg *current = 0;
 	float64 suppressGain = 1.;
 	switch (type) {
-	case OverviewAudios:
+	case OverviewVoiceFiles:
 		current = &_audioData[_audioCurrent];
 		suppressGain = suppressAllGain;
 		break;
-	case OverviewDocuments:
+	case OverviewFiles:
 		current = &_songData[_songCurrent];
 		suppressGain = suppressSongGain * cSongVolume();
 		break;
@@ -567,14 +569,14 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
 			alSourcePlay(current->source);
 			if (!checkCurrentALError(type)) return;
 		}
-		if (type == OverviewAudios) emit suppressSong();
+		if (type == OverviewVoiceFiles) emit suppressSong();
 	} break;
 	case AudioPlayerStarting:
 	case AudioPlayerResuming:
 	case AudioPlayerPlaying:
 		current->state = AudioPlayerPausing;
 		updateCurrentStarted(type);
-		if (type == OverviewAudios) emit unsuppressSong();
+		if (type == OverviewVoiceFiles) emit unsuppressSong();
 	break;
 	case AudioPlayerFinishing: current->state = AudioPlayerPausing; break;
 	}
@@ -584,18 +586,18 @@ void AudioPlayer::pauseresume(MediaOverviewType type, bool fast) {
 void AudioPlayer::seek(int64 position) {
 	QMutexLocker lock(&playerMutex);
 
-	MediaOverviewType type = OverviewDocuments;
+	MediaOverviewType type = OverviewFiles;
 	Msg *current = 0;
 	float64 suppressGain = 1.;
 	AudioMsgId audio;
 	SongMsgId song;
 	switch (type) {
-	case OverviewAudios:
+	case OverviewVoiceFiles:
 		current = &_audioData[_audioCurrent];
 		audio = _audioData[_audioCurrent].audio;
 		suppressGain = suppressAllGain;
 		break;
-	case OverviewDocuments:
+	case OverviewFiles:
 		current = &_songData[_songCurrent];
 		song = _songData[_songCurrent].song;
 		suppressGain = suppressSongGain * cSongVolume();
@@ -629,7 +631,7 @@ void AudioPlayer::seek(int64 position) {
 	case AudioPlayerPlaying:
 		current->state = AudioPlayerPausing;
 		updateCurrentStarted(type);
-		if (type == OverviewAudios) emit unsuppressSong();
+		if (type == OverviewVoiceFiles) emit unsuppressSong();
 		break;
 	case AudioPlayerFinishing:
 	case AudioPlayerStopped:
@@ -638,8 +640,8 @@ void AudioPlayer::seek(int64 position) {
 	case AudioPlayerStoppedAtStart:
 		lock.unlock();
 		switch (type) {
-		case OverviewAudios: if (audio) return play(audio, position);
-		case OverviewDocuments: if (song) return play(song, position);
+		case OverviewVoiceFiles: if (audio) return play(audio, position);
+		case OverviewFiles: if (song) return play(song, position);
 		}
 	}
 	emit faderOnTimer();
@@ -647,7 +649,7 @@ void AudioPlayer::seek(int64 position) {
 
 void AudioPlayer::stop(MediaOverviewType type) {
 	switch (type) {
-	case OverviewAudios: {
+	case OverviewVoiceFiles: {
 		AudioMsgId current;
 		{
 			QMutexLocker lock(&playerMutex);
@@ -657,7 +659,7 @@ void AudioPlayer::stop(MediaOverviewType type) {
 		if (current) emit updated(current);
 	} break;
 
-	case OverviewDocuments: {
+	case OverviewFiles: {
 		SongMsgId current;
 		{
 			QMutexLocker lock(&playerMutex);
@@ -754,8 +756,8 @@ void AudioPlayer::resumeDevice() {
 AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) {
 	connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart()));
 	connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool)));
-	connect(_capture, SIGNAL(done(QByteArray,qint32)), this, SIGNAL(onDone(QByteArray,qint32)));
-	connect(_capture, SIGNAL(update(qint16,qint32)), this, SIGNAL(onUpdate(qint16,qint32)));
+	connect(_capture, SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SIGNAL(onDone(QByteArray,VoiceWaveform,qint32)));
+	connect(_capture, SIGNAL(update(quint16,qint32)), this, SIGNAL(onUpdate(quint16,qint32)));
 	connect(_capture, SIGNAL(error()), this, SIGNAL(onError()));
 	connect(&_captureThread, SIGNAL(started()), _capture, SLOT(onInit()));
 	connect(&_captureThread, SIGNAL(finished()), _capture, SLOT(deleteLater()));
@@ -1109,19 +1111,18 @@ protected:
 
 };
 
-static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16;
-static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO;
-static const int32 _toChannels = 2;
-class FFMpegLoader : public AudioPlayerLoader {
+class AbstractFFMpegLoader : public AudioPlayerLoader {
 public:
 
-	FFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data),
-		freq(AudioVoiceMsgFrequency), fmt(AL_FORMAT_STEREO16),
-		sampleSize(2 * sizeof(short)), srcRate(AudioVoiceMsgFrequency), dstRate(AudioVoiceMsgFrequency),
-		maxResampleSamples(1024), dstSamplesData(0), len(0),
-		ioBuffer(0), ioContext(0), fmtContext(0), codec(0), codecContext(0), streamId(0), frame(0), swrContext(0),
-		_opened(false) {
-		frame = av_frame_alloc();
+	AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data)
+		, freq(AudioVoiceMsgFrequency)
+		, len(0)
+		, ioBuffer(0)
+		, ioContext(0)
+		, fmtContext(0)
+		, codec(0)
+		, streamId(0)
+		, _opened(false) {
 	}
 
 	bool open(qint64 position = 0) {
@@ -1129,31 +1130,32 @@ public:
 			return false;
 		}
 
+		int res = 0;
+		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+
 		ioBuffer = (uchar*)av_malloc(AVBlockSize);
 		if (data.isEmpty()) {
-			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegLoader::_read_file, 0, &FFMpegLoader::_seek_file);
+			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file);
 		} else {
-			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegLoader::_read_data, 0, &FFMpegLoader::_seek_data);
+			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data);
 		}
 		fmtContext = avformat_alloc_context();
 		if (!fmtContext) {
-			LOG(("Audio Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
+			DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
 			return false;
 		}
 		fmtContext->pb = ioContext;
 
-		int res = 0;
-		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
 		if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) {
 			ioBuffer = 0;
 
-			LOG(("Audio Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+			DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 			return false;
 		}
 		_opened = true;
 
 		if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) {
-			LOG(("Audio Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+			DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
 			return false;
 		}
 
@@ -1163,20 +1165,130 @@ public:
 			return false;
 		}
 
-		// Get a pointer to the codec context for the audio stream
-		codecContext = fmtContext->streams[streamId]->codec;
-		av_opt_set_int(codecContext, "refcounted_frames", 1, 0);
-		if ((res = avcodec_open2(codecContext, codec, 0)) < 0) {
-			LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-			return false;
-		}
-
-		freq = codecContext->sample_rate;
+		freq = fmtContext->streams[streamId]->codec->sample_rate;
 		if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
 			len = (fmtContext->duration * freq) / AV_TIME_BASE;
 		} else {
 			len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
 		}
+
+		return true;
+	}
+
+	int64 duration() {
+		return len;
+	}
+
+	int32 frequency() {
+		return freq;
+	}
+
+	~AbstractFFMpegLoader() {
+		if (ioContext) av_free(ioContext);
+		if (_opened) {
+			avformat_close_input(&fmtContext);
+		} else if (ioBuffer) {
+			av_free(ioBuffer);
+		}
+		if (fmtContext) avformat_free_context(fmtContext);
+	}
+
+protected:
+
+	int32 freq;
+	int64 len;
+
+	uchar *ioBuffer;
+	AVIOContext *ioContext;
+	AVFormatContext *fmtContext;
+	AVCodec *codec;
+	int32 streamId;
+
+	bool _opened;
+
+private:
+
+	static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
+		AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
+
+		int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
+		if (nbytes <= 0) {
+			return 0;
+		}
+
+		memcpy(buf, l->data.constData() + l->dataPos, nbytes);
+		l->dataPos += nbytes;
+		return nbytes;
+	}
+
+	static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
+		AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
+
+		int32 newPos = -1;
+		switch (whence) {
+		case SEEK_SET: newPos = offset; break;
+		case SEEK_CUR: newPos = l->dataPos + offset; break;
+		case SEEK_END: newPos = l->data.size() + offset; break;
+		}
+		if (newPos < 0 || newPos > l->data.size()) {
+			return -1;
+		}
+		l->dataPos = newPos;
+		return l->dataPos;
+	}
+
+	static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
+		AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
+		return int(l->f.read((char*)(buf), buf_size));
+	}
+
+	static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
+		AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
+
+		switch (whence) {
+		case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
+		case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
+		case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
+		}
+		return -1;
+	}
+};
+
+static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16;
+static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO;
+static const int32 _toChannels = 2;
+class FFMpegLoader : public AbstractFFMpegLoader {
+public:
+
+	FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data)
+		, sampleSize(2 * sizeof(uint16))
+		, fmt(AL_FORMAT_STEREO16)
+		, srcRate(AudioVoiceMsgFrequency)
+		, dstRate(AudioVoiceMsgFrequency)
+		, maxResampleSamples(1024)
+		, dstSamplesData(0)
+		, codecContext(0)
+		, frame(0)
+		, swrContext(0) {
+		frame = av_frame_alloc();
+	}
+
+	bool open(qint64 position = 0) {
+		if (!AbstractFFMpegLoader::open(position)) {
+			return false;
+		}
+
+		int res = 0;
+		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+
+		// Get a pointer to the codec context for the audio stream
+		av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0);
+		if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) {
+			LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+			return false;
+		}
+		codecContext = fmtContext->streams[streamId]->codec;
+
 		uint64_t layout = codecContext->channel_layout;
 		inputFormat = codecContext->sample_fmt;
 		switch (layout) {
@@ -1185,7 +1297,7 @@ public:
 			case AV_SAMPLE_FMT_U8:
 			case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break;
 			case AV_SAMPLE_FMT_S16:
-			case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = 2; break;
+			case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break;
 			default:
 				sampleSize = -1; // convert needed
 				break;
@@ -1193,8 +1305,8 @@ public:
 			break;
 		case AV_CH_LAYOUT_STEREO:
 			switch (inputFormat) {
-			case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = sizeof(short); break;
-			case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(short); break;
+			case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break;
+			case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break;
 			default:
 				sampleSize = -1; // convert needed
 				break;
@@ -1256,14 +1368,6 @@ public:
 		return true;
 	}
 
-	int64 duration() {
-		return len;
-	}
-
-	int32 frequency() {
-		return freq;
-	}
-
 	int32 format() {
 		return fmt;
 	}
@@ -1326,7 +1430,6 @@ public:
 	}
 
 	~FFMpegLoader() {
-		if (ioContext) av_free(ioContext);
 		if (codecContext) avcodec_close(codecContext);
 		if (swrContext) swr_free(&swrContext);
 		if (dstSamplesData) {
@@ -1335,80 +1438,25 @@ public:
 			}
 			av_freep(&dstSamplesData);
 		}
-		if (_opened) {
-			avformat_close_input(&fmtContext);
-		} else if (ioBuffer) {
-			av_free(ioBuffer);
-		}
-		if (fmtContext) avformat_free_context(fmtContext);
 		av_frame_free(&frame);
 	}
 
+protected:
+	int32 sampleSize;
+
 private:
 
-	int32 freq, fmt;
-	int32 sampleSize, srcRate, dstRate, maxResampleSamples;
+	int32 fmt;
+	int32 srcRate, dstRate, maxResampleSamples;
 	uint8_t **dstSamplesData;
-	int64 len;
 
-	uchar *ioBuffer;
-	AVIOContext *ioContext;
-	AVFormatContext *fmtContext;
-	AVCodec *codec;
 	AVCodecContext *codecContext;
 	AVPacket avpkt;
-	int32 streamId;
 	AVSampleFormat inputFormat;
 	AVFrame *frame;
 
 	SwrContext *swrContext;
 
-	bool _opened;
-
-	static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
-		FFMpegLoader *l = reinterpret_cast<FFMpegLoader*>(opaque);
-
-		int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
-		if (nbytes <= 0) {
-			return 0;
-		}
-
-		memcpy(buf, l->data.constData() + l->dataPos, nbytes);
-		l->dataPos += nbytes;
-		return nbytes;
-	}
-
-	static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
-		FFMpegLoader *l = reinterpret_cast<FFMpegLoader*>(opaque);
-
-		int32 newPos = -1;
-		switch (whence) {
-		case SEEK_SET: newPos = offset; break;
-		case SEEK_CUR: newPos = l->dataPos + offset; break;
-		case SEEK_END: newPos = l->data.size() + offset; break;
-		}
-		if (newPos < 0 || newPos > l->data.size()) {
-			return -1;
-		}
-		l->dataPos = newPos;
-		return l->dataPos;
-	}
-
-	static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
-		FFMpegLoader *l = reinterpret_cast<FFMpegLoader*>(opaque);
-		return int(l->f.read((char*)(buf), buf_size));
-	}
-
-	static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
-		FFMpegLoader *l = reinterpret_cast<FFMpegLoader*>(opaque);
-
-		switch (whence) {
-		case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
-		case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
-		case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
-		}
-		return -1;
-	}
 };
 
 AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) {
@@ -1436,7 +1484,7 @@ void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) {
 		voice->_audioData[voice->_audioCurrent].loading = true;
 	}
 
-	loadData(OverviewAudios, static_cast<const void*>(&audio), position);
+	loadData(OverviewVoiceFiles, static_cast<const void*>(&audio), position);
 }
 
 void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) {
@@ -1452,13 +1500,13 @@ void AudioPlayerLoaders::onStart(const SongMsgId &song, qint64 position) {
 		voice->_songData[voice->_songCurrent].loading = true;
 	}
 
-	loadData(OverviewDocuments, static_cast<const void*>(&song), position);
+	loadData(OverviewFiles, static_cast<const void*>(&song), position);
 }
 
 void AudioPlayerLoaders::clear(MediaOverviewType type) {
 	switch (type) {
-	case OverviewAudios: clearAudio(); break;
-	case OverviewDocuments: clearSong(); break;
+	case OverviewVoiceFiles: clearAudio(); break;
+	case OverviewFiles: clearSong(); break;
 	}
 }
 
@@ -1469,8 +1517,8 @@ void AudioPlayerLoaders::setStoppedState(AudioPlayer::Msg *m, AudioPlayerState s
 
 void AudioPlayerLoaders::emitError(MediaOverviewType type) {
 	switch (type) {
-	case OverviewAudios: emit error(clearAudio()); break;
-	case OverviewDocuments: emit error(clearSong()); break;
+	case OverviewVoiceFiles: emit error(clearAudio()); break;
+	case OverviewFiles: emit error(clearSong()); break;
 	}
 }
 
@@ -1491,11 +1539,11 @@ SongMsgId AudioPlayerLoaders::clearSong() {
 }
 
 void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) {
-	loadData(OverviewAudios, static_cast<const void*>(&audio), 0);
+	loadData(OverviewVoiceFiles, static_cast<const void*>(&audio), 0);
 }
 
 void AudioPlayerLoaders::onLoad(const SongMsgId &song) {
-	loadData(OverviewDocuments, static_cast<const void*>(&song), 0);
+	loadData(OverviewFiles, static_cast<const void*>(&song), 0);
 }
 
 void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qint64 position) {
@@ -1608,8 +1656,8 @@ void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId, qin
 				audioPlayer()->resumeDevice();
 
 				switch (type) {
-				case OverviewAudios: alSourcef(m->source, AL_GAIN, suppressAllGain); break;
-				case OverviewDocuments: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break;
+				case OverviewVoiceFiles: alSourcef(m->source, AL_GAIN, suppressAllGain); break;
+				case OverviewFiles: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break;
 				}
 				if (!_checkALError()) {
 					setStoppedState(m, AudioPlayerStoppedAtError);
@@ -1643,7 +1691,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
 	AudioPlayer::Msg *m = 0;
 	AudioPlayerLoader **l = 0;
 	switch (type) {
-	case OverviewAudios: {
+	case OverviewVoiceFiles: {
 		AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]);
 		const AudioMsgId &audio(*static_cast<const AudioMsgId*>(objId));
 		if (msg.audio != audio || !msg.loading) {
@@ -1654,7 +1702,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
 		l = &_audioLoader;
 		isGoodId = (_audio == audio);
 	} break;
-	case OverviewDocuments: {
+	case OverviewFiles: {
 		AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]);
 		const SongMsgId &song(*static_cast<const SongMsgId*>(objId));
 		if (msg.song != song || !msg.loading) {
@@ -1676,15 +1724,15 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
 		delete *l;
 		*l = 0;
 		switch (type) {
-		case OverviewAudios: _audio = AudioMsgId(); break;
-		case OverviewDocuments: _song = SongMsgId(); break;
+		case OverviewVoiceFiles: _audio = AudioMsgId(); break;
+		case OverviewFiles: _song = SongMsgId(); break;
 		}
 	}
 
 	if (!*l) {
 		switch (type) {
-		case OverviewAudios: _audio = *static_cast<const AudioMsgId*>(objId); break;
-		case OverviewDocuments: _song = *static_cast<const SongMsgId*>(objId); break;
+		case OverviewVoiceFiles: _audio = *static_cast<const AudioMsgId*>(objId); break;
+		case OverviewFiles: _song = *static_cast<const SongMsgId*>(objId); break;
 		}
 
 //		QByteArray header = m->data.mid(0, 8);
@@ -1737,13 +1785,13 @@ AudioPlayer::Msg *AudioPlayerLoaders::checkLoader(MediaOverviewType type) {
 	AudioPlayer::Msg *m = 0;
 	AudioPlayerLoader **l = 0;
 	switch (type) {
-	case OverviewAudios: {
+	case OverviewVoiceFiles: {
 		AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]);
 		isGoodId = (msg.audio == _audio);
 		l = &_audioLoader;
 		m = &msg;
 	} break;
-	case OverviewDocuments: {
+	case OverviewFiles: {
 		AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]);
 		isGoodId = (msg.song == _song);
 		l = &_songLoader;
@@ -1799,10 +1847,30 @@ void AudioPlayerLoaders::onCancel(const SongMsgId &song) {
 }
 
 struct AudioCapturePrivate {
-	AudioCapturePrivate() :
-		device(0), fmt(0), ioBuffer(0), ioContext(0), fmtContext(0), stream(0), codec(0), codecContext(0), opened(false),
-		srcSamples(0), dstSamples(0), maxDstSamples(0), dstSamplesSize(0), fullSamples(0), srcSamplesData(0), dstSamplesData(0),
-		swrContext(0), lastUpdate(0), level(0), dataPos(0) {
+	AudioCapturePrivate()
+		: device(0)
+		, fmt(0)
+		, ioBuffer(0)
+		, ioContext(0)
+		, fmtContext(0)
+		, stream(0)
+		, codec(0)
+		, codecContext(0)
+		, opened(false)
+		, srcSamples(0)
+		, dstSamples(0)
+		, maxDstSamples(0)
+		, dstSamplesSize(0)
+		, fullSamples(0)
+		, srcSamplesData(0)
+		, dstSamplesData(0)
+		, swrContext(0)
+		, lastUpdate(0)
+		, levelMax(0)
+		, waveformMod(0)
+		, waveformEach(AudioVoiceMsgFrequency / 100)
+		, waveformPeak(0)
+		, dataPos(0) {
 	}
 	ALCdevice *device;
 	AVOutputFormat *fmt;
@@ -1819,11 +1887,15 @@ struct AudioCapturePrivate {
 	SwrContext *swrContext;
 
 	int32 lastUpdate;
-	int64 level;
+	uint16 levelMax;
 
 	QByteArray data;
 	int32 dataPos;
 
+	int64 waveformMod, waveformEach;
+	uint16 waveformPeak;
+	QVector<uchar> waveform;
+
 	static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
 		AudioCapturePrivate *l = reinterpret_cast<AudioCapturePrivate*>(opaque);
 
@@ -2035,6 +2107,9 @@ void AudioCaptureInner::onStop(bool needResult) {
 			d->fullSamples = 0;
 			d->dataPos = 0;
 			d->data.clear();
+			d->waveformMod = 0;
+			d->waveformPeak = 0;
+			d->waveform.clear();
 		} else {
 			float64 coef = 1. / fadeSamples, fadedFrom = 0;
 			for (short *ptr = ((short*)_captured.data()) + capturedSamples, *end = ptr - fadeSamples; ptr != end; ++fadedFrom) {
@@ -2056,6 +2131,9 @@ void AudioCaptureInner::onStop(bool needResult) {
 				d->fullSamples = 0;
 				d->dataPos = 0;
 				d->data.clear();
+				d->waveformMod = 0;
+				d->waveformPeak = 0;
+				d->waveform.clear();
 			}
 		}
 	}
@@ -2068,7 +2146,37 @@ void AudioCaptureInner::onStop(bool needResult) {
 	}
 
 	QByteArray result = d->fullSamples ? d->data : QByteArray();
+	VoiceWaveform waveform;
 	qint32 samples = d->fullSamples;
+	if (samples && !d->waveform.isEmpty()) {
+		int64 count = d->waveform.size(), sum = 0;
+		if (count >= WaveformSamplesCount) {
+			QVector<uint16> peaks;
+			peaks.reserve(WaveformSamplesCount);
+
+			uint16 peak = 0;
+			for (int32 i = 0; i < count; ++i) {
+				uint16 sample = uint16(d->waveform.at(i)) * 256;
+				if (peak < sample) {
+					peak = sample;
+				}
+				sum += WaveformSamplesCount;
+				if (sum >= count) {
+					sum -= count;
+					peaks.push_back(peak);
+					peak = 0;
+				}
+			}
+
+			int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
+			peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
+
+			waveform.resize(peaks.size());
+			for (int32 i = 0, l = peaks.size(); i != l; ++i) {
+				waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
+			}
+		}
+	}
 	if (d->device) {
 		alcCaptureStop(d->device);
 		alcCaptureCloseDevice(d->device);
@@ -2116,12 +2224,16 @@ void AudioCaptureInner::onStop(bool needResult) {
 		d->codec = 0;
 
 		d->lastUpdate = 0;
-		d->level = 0;
+		d->levelMax = 0;
 
 		d->dataPos = 0;
 		d->data.clear();
+
+		d->waveformMod = 0;
+		d->waveformPeak = 0;
+		d->waveform.clear();
 	}
-	if (needResult) emit done(result, samples);
+	if (needResult) emit done(result, waveform, samples);
 }
 
 void AudioCaptureInner::onTimeout() {
@@ -2155,18 +2267,20 @@ void AudioCaptureInner::onTimeout() {
 		int32 levelindex = d->fullSamples + (s / sizeof(short));
 		for (const short *ptr = (const short*)(_captured.constData() + s), *end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) {
 			if (levelindex > skipSamples) {
+				uint16 value = qAbs(*ptr);
 				if (levelindex < skipSamples + fadeSamples) {
-					d->level += qRound(qAbs(*ptr) * float64(levelindex - skipSamples) / fadeSamples);
-				} else {
-					d->level += qAbs(*ptr);
+					value = qRound(value * float64(levelindex - skipSamples) / fadeSamples);
+				}
+				if (d->levelMax < value) {
+					d->levelMax = value;
 				}
 			}
 		}
 		qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate;
 		if (samplesSinceUpdate > AudioVoiceMsgUpdateView * AudioVoiceMsgFrequency / 1000) {
-			emit update(d->level / samplesSinceUpdate, samplesFull);
+			emit update(d->levelMax, samplesFull);
 			d->lastUpdate = samplesFull;
-			d->level = 0;
+			d->levelMax = 0;
 		}
 		// Write frames
 		int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0;
@@ -2206,7 +2320,7 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
 	if (d->fullSamples < skipSamples + fadeSamples) {
 		int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples);
 		float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples;
-		short *ptr = (short*)srcSamplesData[0], *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt;
+		short *ptr = srcSamplesDataChannel, *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt;
 		for (; ptr != zeroEnd; ++ptr, ++fadedFrom) {
 			*ptr = 0;
 		}
@@ -2215,6 +2329,19 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
 		}
 	}
 
+	d->waveform.reserve(d->waveform.size() + (samplesCnt / d->waveformEach) + 1);
+	for (short *ptr = srcSamplesDataChannel, *end = ptr + samplesCnt; ptr != end; ++ptr) {
+		uint16 value = qAbs(*ptr);
+		if (d->waveformPeak < value) {
+			d->waveformPeak = value;
+		}
+		if (++d->waveformMod == d->waveformEach) {
+			d->waveformMod -= d->waveformEach;
+			d->waveform.push_back(uchar(d->waveformPeak / 256));
+			d->waveformPeak = 0;
+		}
+	}
+
 	// Convert to final format
 
 	d->dstSamples = av_rescale_rnd(swr_get_delay(d->swrContext, d->codecContext->sample_rate) + d->srcSamples, d->codecContext->sample_rate, d->codecContext->sample_rate, AV_ROUND_UP);
@@ -2269,65 +2396,25 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
 	av_frame_free(&frame);
 }
 
-class FFMpegAttributesReader : public AudioPlayerLoader {
+class FFMpegAttributesReader : public AbstractFFMpegLoader {
 public:
 
-	FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data),
-		ioBuffer(0), ioContext(0), fmtContext(0), codec(0), streamId(0),
-		_opened(false) {
+	FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) {
 	}
 
 	bool open(qint64 position = 0) {
-		if (!AudioPlayerLoader::openFile()) {
+		if (!AbstractFFMpegLoader::openFile()) {
 			return false;
 		}
 
-		ioBuffer = (uchar*)av_malloc(AVBlockSize);
-		if (data.isEmpty()) {
-			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegAttributesReader::_read_file, 0, &FFMpegAttributesReader::_seek_file);
-		} else {
-			ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegAttributesReader::_read_data, 0, &FFMpegAttributesReader::_seek_data);
-		}
-		fmtContext = avformat_alloc_context();
-		if (!fmtContext) {
-			DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(fname).arg(data.size()));
-			return false;
-		}
-		fmtContext->pb = ioContext;
-
 		int res = 0;
 		char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
-		if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) {
-            ioBuffer = 0;
 
-            DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
+		int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
+		if (videoStreamId >= 0) {
+			DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId)));
 			return false;
 		}
-		_opened = true;
-
-		if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) {
-			DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
-			return false;
-		}
-
-		streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
-		if (streamId >= 0) {
-			DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId)));
-			return false;
-		}
-
-		streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
-		if (streamId < 0) {
-			DEBUG_LOG(("Audio Read Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId)));
-			return false;
-		}
-
-		freq = fmtContext->streams[streamId]->codec->sample_rate;
-		if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
-			len = (fmtContext->duration * freq) / AV_TIME_BASE;
-		} else {
-			len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
-		}
 
 		for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) {
 			AVStream *stream = fmtContext->streams[i];
@@ -2370,14 +2457,6 @@ public:
 		//}
 	}
 
-	int64 duration() {
-		return len;
-	}
-
-	int32 frequency() {
-		return freq;
-	}
-
 	int32 format() {
 		return 0;
 	}
@@ -2408,77 +2487,14 @@ public:
 	}
 
 	~FFMpegAttributesReader() {
-		if (ioContext) av_free(ioContext);
-		if (_opened) {
-			avformat_close_input(&fmtContext);
-		} else if (ioBuffer) {
-			av_free(ioBuffer);
-		}
-		if (fmtContext) avformat_free_context(fmtContext);
 	}
 
 private:
 
-	QString fname, data;
-
-	int32 freq;
-	int64 len;
 	QString _title, _performer;
 	QImage _cover;
 	QByteArray _coverBytes, _coverFormat;
 
-	uchar *ioBuffer;
-	AVIOContext *ioContext;
-	AVFormatContext *fmtContext;
-	AVCodec *codec;
-	int32 streamId;
-
-	bool _opened;
-
-	static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
-		FFMpegAttributesReader *l = reinterpret_cast<FFMpegAttributesReader*>(opaque);
-
-		int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
-		if (nbytes <= 0) {
-			return 0;
-		}
-
-		memcpy(buf, l->data.constData() + l->dataPos, nbytes);
-		l->dataPos += nbytes;
-		return nbytes;
-	}
-
-	static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
-		FFMpegAttributesReader *l = reinterpret_cast<FFMpegAttributesReader*>(opaque);
-
-		int32 newPos = -1;
-		switch (whence) {
-		case SEEK_SET: newPos = offset; break;
-		case SEEK_CUR: newPos = l->dataPos + offset; break;
-		case SEEK_END: newPos = l->data.size() + offset; break;
-		}
-		if (newPos < 0 || newPos > l->data.size()) {
-			return -1;
-		}
-		l->dataPos = newPos;
-		return l->dataPos;
-	}
-
-	static int _read_file(void *opaque, uint8_t *buf, int buf_size) {
-		FFMpegAttributesReader *l = reinterpret_cast<FFMpegAttributesReader*>(opaque);
-		return int(l->f.read((char*)(buf), buf_size));
-	}
-
-	static int64_t _seek_file(void *opaque, int64_t offset, int whence) {
-		FFMpegAttributesReader *l = reinterpret_cast<FFMpegAttributesReader*>(opaque);
-
-		switch (whence) {
-		case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
-		case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
-		case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
-		}
-		return -1;
-	}
 };
 
 MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) {
@@ -2489,8 +2505,116 @@ MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteAr
 			cover = reader.cover();
 			coverBytes = reader.coverBytes();
 			coverFormat = reader.coverFormat();
-			return MTP_documentAttributeAudio(MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()));
+			return MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_title | MTPDdocumentAttributeAudio::flag_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring());
 		}
 	}
 	return MTP_documentAttributeFilename(MTP_string(fname));
 }
+
+class FFMpegWaveformCounter : public FFMpegLoader {
+public:
+
+	FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) {
+	}
+
+	bool open(qint64 position = 0) {
+		if (!FFMpegLoader::open(position)) {
+			return false;
+		}
+
+		QByteArray buffer;
+		buffer.reserve(AudioVoiceMsgBufferSize);
+		int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
+		if (duration() < WaveformSamplesCount) {
+			return false;
+		}
+
+		QVector<uint16> peaks;
+		peaks.reserve(WaveformSamplesCount);
+
+		int32 fmt = format();
+		uint16 peak = 0;
+		while (processed < countbytes) {
+			buffer.resize(0);
+
+			int64 samples = 0;
+			int res = readMore(buffer, samples);
+			if (res < 0) {
+				break;
+			}
+			if (buffer.isEmpty()) {
+				continue;
+			}
+
+			const char *data = buffer.data();
+			if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) {
+				for (int32 i = 0, l = buffer.size(); i + sizeof(uchar) <= l;) {
+					uint16 sample = qAbs((int32(*(uchar*)(data + i)) - 128) * 256);
+					if (peak < sample) {
+						peak = sample;
+					}
+
+					i += sizeof(uchar);
+					sumbytes += WaveformSamplesCount;
+					if (sumbytes >= countbytes) {
+						sumbytes -= countbytes;
+						peaks.push_back(peak);
+						peak = 0;
+					}
+				}
+			} else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) {
+				for (int32 i = 0, l = buffer.size(); i + sizeof(uint16) <= l;) {
+					uint16 sample = qAbs(int32(*(int16*)(data + i)));
+					if (peak < sample) {
+						peak = sample;
+					}
+
+					i += sizeof(uint16);
+					sumbytes += sizeof(uint16) * WaveformSamplesCount;
+					if (sumbytes >= countbytes) {
+						sumbytes -= countbytes;
+						peaks.push_back(peak);
+						peak = 0;
+					}
+				}
+			}
+			processed += sampleSize * samples;
+		}
+		if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) {
+			peaks.push_back(peak);
+		}
+
+		if (peaks.isEmpty()) {
+			return false;
+		}
+
+		int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
+		peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
+
+		result.resize(peaks.size());
+		for (int32 i = 0, l = peaks.size(); i != l; ++i) {
+			result[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
+		}
+
+		return true;
+	}
+
+	const VoiceWaveform &waveform() const {
+		return result;
+	}
+
+	~FFMpegWaveformCounter() {
+	}
+
+private:
+	VoiceWaveform result;
+
+};
+
+VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) {
+	FFMpegWaveformCounter counter(file, data);
+	if (counter.open()) {
+		return counter.waveform();
+	}
+	return VoiceWaveform();
+}
diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h
index e21c273f7..f9259906e 100644
--- a/Telegram/SourceFiles/audio.h
+++ b/Telegram/SourceFiles/audio.h
@@ -56,7 +56,7 @@ public:
 	void play(const AudioMsgId &audio, int64 position = 0);
 	void play(const SongMsgId &song, int64 position = 0);
 	void pauseresume(MediaOverviewType type, bool fast = false);
-	void seek(int64 position); // type == OverviewDocuments
+	void seek(int64 position); // type == OverviewFiles
 	void stop(MediaOverviewType type);
 
 	void stopAndClear();
@@ -201,8 +201,8 @@ signals:
 	void captureOnStart();
 	void captureOnStop(bool needResult);
 
-	void onDone(QByteArray data, qint32 samples);
-	void onUpdate(qint16 level, qint32 samples);
+	void onDone(QByteArray data, VoiceWaveform waveform, qint32 samples);
+	void onUpdate(quint16 level, qint32 samples);
 	void onError();
 
 private:
@@ -338,8 +338,8 @@ public:
 signals:
 
 	void error();
-	void update(qint16 level, qint32 samples);
-	void done(QByteArray data, qint32 samples);
+	void update(quint16 level, qint32 samples);
+	void done(QByteArray data, VoiceWaveform waveform, qint32 samples);
 
 public slots:
 
@@ -360,3 +360,4 @@ private:
 };
 
 MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat);
+VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data);
diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp
index a94fdd44d..27b2ce7ff 100644
--- a/Telegram/SourceFiles/boxes/connectionbox.cpp
+++ b/Telegram/SourceFiles/boxes/connectionbox.cpp
@@ -313,9 +313,11 @@ void AutoDownloadBox::onSave() {
 		bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups));
 		cSetAutoDownloadAudio(autoDownloadAudio);
 		if (enabledPrivate || enabledGroups) {
-			const AudiosData &data(App::audiosData());
-			for (AudiosData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
-				i.value()->automaticLoadSettingsChanged();
+			const DocumentsData &data(App::documentsData());
+			for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
+				if (i.value()->voice()) {
+					i.value()->automaticLoadSettingsChanged();
+				}
 			}
 		}
 		changed = true;
@@ -328,7 +330,9 @@ void AutoDownloadBox::onSave() {
 		if (enabledPrivate || enabledGroups) {
 			const DocumentsData &data(App::documentsData());
 			for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
-				i.value()->automaticLoadSettingsChanged();
+				if (i.value()->isAnimation()) {
+					i.value()->automaticLoadSettingsChanged();
+				}
 			}
 			Notify::automaticLoadSettingsChangedGif();
 		}
diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h
index f5797848c..1816d479c 100644
--- a/Telegram/SourceFiles/config.h
+++ b/Telegram/SourceFiles/config.h
@@ -20,8 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 */
 #pragma once
 
-static const int32 AppVersion = 9021;
-static const wchar_t *AppVersionStr = L"0.9.21";
+static const int32 AppVersion = 9022;
+static const wchar_t *AppVersionStr = L"0.9.22";
 static const bool DevVersion = true;
 //#define BETA_VERSION (9019002ULL) // just comment this line to build public version
 
@@ -118,6 +118,8 @@ enum {
 	AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded
 	AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
 
+	WaveformSamplesCount = 100,
+
 	StickerInMemory = 2 * 1024 * 1024, // 2 Mb stickers hold in memory, auto loaded and displayed inline
 	StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
 
diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp
index e7a700bd3..f968d3d57 100644
--- a/Telegram/SourceFiles/dialogswidget.cpp
+++ b/Telegram/SourceFiles/dialogswidget.cpp
@@ -258,7 +258,7 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a
 	QRect rectForName(nameleft, st::dlgPaddingVer + st::dlgNameTop, namewidth, st::msgNameFont->height);
 
 	// draw chat icon
-	if (peer->isChat()) {
+	if (peer->isChat() || peer->isMegagroup()) {
 		p.drawPixmap(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), App::sprite(), (act ? st::dlgActiveChatImg : st::dlgChatImg));
 		rectForName.setLeft(rectForName.left() + st::dlgImgSkip);
 	} else if (peer->isChannel()) {
diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp
index f4b9ac2a5..737e70d0b 100644
--- a/Telegram/SourceFiles/dropdown.cpp
+++ b/Telegram/SourceFiles/dropdown.cpp
@@ -1698,6 +1698,7 @@ bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool fo
 
 void StickerPanInner::refreshSavedGifs() {
 	if (_showingSavedGifs) {
+		_settings.hide();
 		clearInlineRows(false);
 		if (_showingInlineItems) {
 			const SavedGifs &saved(cSavedGifs());
@@ -1919,6 +1920,7 @@ int32 StickerPanInner::refreshInlineRows(UserData *bot, const InlineResults &res
 
 	_showingInlineItems = true;
 	_showingSavedGifs = false;
+	_settings.hide();
 
 	int32 count = results.size(), from = validateExistingInlineRows(results), added = 0;
 
diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp
index 607a2e91c..c93f8dbb6 100644
--- a/Telegram/SourceFiles/fileuploader.cpp
+++ b/Telegram/SourceFiles/fileuploader.cpp
@@ -32,7 +32,7 @@ FileUploader::FileUploader() : sentSize(0) {
 void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &media) {
 	if (media.type == PreparePhoto) {
 		App::feedPhoto(media.photo, media.photoThumbs);
-	} else if (media.type == PrepareDocument) {
+	} else if (media.type == PrepareDocument || media.type == PrepareAudio) {
 		DocumentData *document;
 		if (media.photoThumbs.isEmpty()) {
 			document = App::feedDocument(media.document);
@@ -40,13 +40,12 @@ void FileUploader::uploadMedia(const FullMsgId &msgId, const ReadyLocalMedia &me
 			document = App::feedDocument(media.document, media.photoThumbs.begin().value());
 		}
 		document->status = FileUploading;
+		if (!media.data.isEmpty()) {
+			document->setData(media.data);
+		}
 		if (!media.file.isEmpty()) {
 			document->setLocation(FileLocation(StorageFilePartial, media.file));
 		}
-	} else if (media.type == PrepareAudio) {
-		AudioData *audio = App::feedAudio(media.audio);
-		audio->status = FileUploading;
-		audio->setData(media.data);
 	}
 	queue.insert(msgId, File(media));
 	sendNext();
@@ -56,7 +55,7 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file)
 	if (file->type == PreparePhoto) {
 		PhotoData *photo = App::feedPhoto(file->photo, file->photoThumbs);
 		photo->uploadingData = new PhotoData::UploadingData(file->partssize);
-	} else if (file->type == PrepareDocument) {
+	} else if (file->type == PrepareDocument || file->type == PrepareAudio) {
 		DocumentData *document;
 		if (file->thumb.isNull()) {
 			document = App::feedDocument(file->document);
@@ -64,13 +63,12 @@ void FileUploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file)
 			document = App::feedDocument(file->document, file->thumb);
 		}
 		document->status = FileUploading;
+		if (!file->content.isEmpty()) {
+			document->setData(file->content);
+		}
 		if (!file->filepath.isEmpty()) {
 			document->setLocation(FileLocation(StorageFilePartial, file->filepath));
 		}
-	} else if (file->type == PrepareAudio) {
-		AudioData *audio = App::feedAudio(file->audio);
-		audio->status = FileUploading;
-		audio->setData(file->content);
 	}
 	queue.insert(msgId, File(file));
 	sendNext();
@@ -87,12 +85,6 @@ void FileUploader::currentFailed() {
 				doc->status = FileUploadFailed;
 			}
 			emit documentFailed(j.key());
-		} else if (j->type() == PrepareAudio) {
-			AudioData *audio = App::audio(j->id());
-			if (audio->status == FileUploading) {
-				audio->status = FileUploadFailed;
-			}
-			emit audioFailed(j.key());
 		}
 		queue.erase(j);
 	}
@@ -133,7 +125,7 @@ void FileUploader::sendNext() {
 	if (!uploading.msg) {
 		uploading = i.key();
 	} else if (i == queue.end()) {
-		i = queue.begin(); 
+		i = queue.begin();
 		uploading = i.key();
 	}
 	int todc = 0;
@@ -150,7 +142,7 @@ void FileUploader::sendNext() {
 			if (requestsSent.isEmpty() && docRequestsSent.isEmpty()) {
 				if (i->type() == PreparePhoto) {
 					emit photoReady(uploading, MTP_inputFile(MTP_long(i->id()), MTP_int(i->partsCount), MTP_string(i->filename()), MTP_string(i->file ? i->file->filemd5 : i->media.jpeg_md5)));
-				} else if (i->type() == PrepareDocument) {
+				} else if (i->type() == PrepareDocument || i->type() == PrepareAudio) {
 					QByteArray docMd5(32, Qt::Uninitialized);
 					hashMd5Hex(i->md5Hash.result(), docMd5.data());
 
@@ -160,12 +152,6 @@ void FileUploader::sendNext() {
 					} else {
 						emit documentReady(uploading, doc);
 					}
-				} else if (i->type() == PrepareAudio) {
-					QByteArray audioMd5(32, Qt::Uninitialized);
-					hashMd5Hex(i->md5Hash.result(), audioMd5.data());
-
-					MTPInputFile audio = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename())) : MTP_inputFile(MTP_long(i->id()), MTP_int(i->docPartsCount), MTP_string(i->filename()), MTP_string(audioMd5));
-					emit audioReady(uploading, audio);
 				}
 				queue.remove(uploading);
 				uploading = FullMsgId();
@@ -212,7 +198,7 @@ void FileUploader::sendNext() {
 		i->docSentParts++;
 	} else {
 		UploadFileParts::iterator part = parts.begin();
-	
+
 		mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_string(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl[todc]);
 		requestsSent.insert(requestId, part.value());
 		dcMap.insert(requestId, todc);
@@ -303,7 +289,7 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
 					photo->uploadingData->offset = k->fileSentSize;
 				}
 				emit photoProgress(k.key());
-			} else if (k->type() == PrepareDocument) {
+			} else if (k->type() == PrepareDocument || k->type() == PrepareAudio) {
 				DocumentData *doc = App::document(k->id());
 				if (doc->uploading()) {
 					doc->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize;
@@ -312,15 +298,6 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
 					}
 				}
 				emit documentProgress(k.key());
-			} else if (k->type() == PrepareAudio) {
-				AudioData *audio = App::audio(k->id());
-				if (audio->uploading()) {
-					audio->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize;
-					if (audio->uploadOffset > audio->size) {
-						audio->uploadOffset = audio->size;
-					}
-				}
-				emit audioProgress(k.key());
 			}
 		}
 	}
diff --git a/Telegram/SourceFiles/fileuploader.h b/Telegram/SourceFiles/fileuploader.h
index 895e7c677..8e038fd9d 100644
--- a/Telegram/SourceFiles/fileuploader.h
+++ b/Telegram/SourceFiles/fileuploader.h
@@ -51,15 +51,12 @@ signals:
 	void photoReady(const FullMsgId &msgId, const MTPInputFile &file);
 	void documentReady(const FullMsgId &msgId, const MTPInputFile &file);
 	void thumbDocumentReady(const FullMsgId &msgId, const MTPInputFile &file, const MTPInputFile &thumb);
-	void audioReady(const FullMsgId &msgId, const MTPInputFile &file);
 
 	void photoProgress(const FullMsgId &msgId);
 	void documentProgress(const FullMsgId &msgId);
-	void audioProgress(const FullMsgId &msgId);
 
 	void photoFailed(const FullMsgId &msgId);
 	void documentFailed(const FullMsgId &msgId);
-	void audioFailed(const FullMsgId &msgId);
 
 private:
 
@@ -138,7 +135,7 @@ private:
 	QMap<mtpRequestId, int32> dcMap;
 	uint32 sentSize;
 	uint32 sentSizes[MTPUploadSessionsCount];
-	
+
 	FullMsgId uploading, _paused;
 	Queue queue;
 	Queue uploaded;
diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp
index 0837eaeb8..00f31d304 100644
--- a/Telegram/SourceFiles/gui/text.cpp
+++ b/Telegram/SourceFiles/gui/text.cpp
@@ -455,6 +455,25 @@ public:
 		}
 	}
 
+	bool readSkipBlockCommand() {
+		const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF);
+		if (afterCmd == ptr) {
+			return false;
+		}
+
+		ushort cmd = (++ptr)->unicode();
+		++ptr;
+
+		switch (cmd) {
+		case TextCommandSkipBlock:
+			createSkipBlock(ptr->unicode(), (ptr + 1)->unicode());
+		break;
+		}
+
+		ptr = afterCmd;
+		return true;
+	}
+
 	bool readCommand() {
 		const QChar *afterCmd = textSkipCommand(ptr, end, links.size() < 0x7FFF);
 		if (afterCmd == ptr) {
@@ -530,7 +549,6 @@ public:
 		} break;
 
 		case TextCommandSkipBlock:
-			createBlock();
 			createSkipBlock(ptr->unicode(), (ptr + 1)->unicode());
 		break;
 
@@ -703,6 +721,13 @@ public:
 			if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
 		}
 		createBlock();
+		if (sumFinished && rich) { // we could've skipped the final skip block command
+			for (; ptr < end; ++ptr) {
+				if (*ptr == TextCommand && readSkipBlockCommand()) {
+					break;
+				}
+			}
+		}
 		removeFlags.clear();
 
 		_t->_links.resize(maxLnkIndex);
diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp
index 869ad4114..8d1ed775a 100644
--- a/Telegram/SourceFiles/history.cpp
+++ b/Telegram/SourceFiles/history.cpp
@@ -352,8 +352,8 @@ bool History::updateTyping(uint64 ms, bool force) {
 			switch (sendActions.begin().value().type) {
 			case SendActionRecordVideo: newTypingStr = peer->isUser() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, sendActions.begin().key()->firstName); break;
 			case SendActionUploadVideo: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, sendActions.begin().key()->firstName); break;
-			case SendActionRecordAudio: newTypingStr = peer->isUser() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, sendActions.begin().key()->firstName); break;
-			case SendActionUploadAudio: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, sendActions.begin().key()->firstName); break;
+			case SendActionRecordVoice: newTypingStr = peer->isUser() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, sendActions.begin().key()->firstName); break;
+			case SendActionUploadVoice: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, sendActions.begin().key()->firstName); break;
 			case SendActionUploadPhoto: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, sendActions.begin().key()->firstName); break;
 			case SendActionUploadFile: newTypingStr = peer->isUser() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, sendActions.begin().key()->firstName); break;
 			case SendActionChooseLocation: newTypingStr = peer->isUser() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, sendActions.begin().key()->firstName); break;
@@ -1249,8 +1249,8 @@ void Histories::regSendAction(History *history, UserData *user, const MTPSendMes
 	case mtpc_sendMessageTypingAction: history->typing[user] = ms + 6000; break;
 	case mtpc_sendMessageRecordVideoAction: history->sendActions.insert(user, SendAction(SendActionRecordVideo, ms + 6000)); break;
 	case mtpc_sendMessageUploadVideoAction: history->sendActions.insert(user, SendAction(SendActionUploadVideo, ms + 6000, action.c_sendMessageUploadVideoAction().vprogress.v)); break;
-	case mtpc_sendMessageRecordAudioAction: history->sendActions.insert(user, SendAction(SendActionRecordAudio, ms + 6000)); break;
-	case mtpc_sendMessageUploadAudioAction: history->sendActions.insert(user, SendAction(SendActionUploadAudio, ms + 6000, action.c_sendMessageUploadAudioAction().vprogress.v)); break;
+	case mtpc_sendMessageRecordAudioAction: history->sendActions.insert(user, SendAction(SendActionRecordVoice, ms + 6000)); break;
+	case mtpc_sendMessageUploadAudioAction: history->sendActions.insert(user, SendAction(SendActionUploadVoice, ms + 6000, action.c_sendMessageUploadAudioAction().vprogress.v)); break;
 	case mtpc_sendMessageUploadPhotoAction: history->sendActions.insert(user, SendAction(SendActionUploadPhoto, ms + 6000, action.c_sendMessageUploadPhotoAction().vprogress.v)); break;
 	case mtpc_sendMessageUploadDocumentAction: history->sendActions.insert(user, SendAction(SendActionUploadFile, ms + 6000, action.c_sendMessageUploadDocumentAction().vprogress.v)); break;
 	case mtpc_sendMessageGeoLocationAction: history->sendActions.insert(user, SendAction(SendActionChooseLocation, ms + 6000)); break;
@@ -1362,20 +1362,6 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo
 			default: badMedia = 1; break;
 			}
 			break;
-		case mtpc_messageMediaVideo:
-			switch (m.vmedia.c_messageMediaVideo().vvideo.type()) {
-			case mtpc_video: break;
-			case mtpc_videoEmpty: badMedia = 2; break;
-			default: badMedia = 1; break;
-			}
-			break;
-		case mtpc_messageMediaAudio:
-			switch (m.vmedia.c_messageMediaAudio().vaudio.type()) {
-			case mtpc_audio: break;
-			case mtpc_audioEmpty: badMedia = 2; break;
-			default: badMedia = 1; break;
-			}
-			break;
 		case mtpc_messageMediaDocument:
 			switch (m.vmedia.c_messageMediaDocument().vdocument.type()) {
 			case mtpc_document: break;
@@ -3054,24 +3040,16 @@ void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, cons
 }
 
 namespace {
-	int32 videoMaxStatusWidth(VideoData *video) {
-		int32 result = st::normalFont->width(formatDownloadText(video->size, video->size));
-		result = qMax(result, st::normalFont->width(formatDurationAndSizeText(video->duration, video->size)));
-		return result;
-	}
-
-	int32 audioMaxStatusWidth(AudioData *audio) {
-		int32 result = st::normalFont->width(formatDownloadText(audio->size, audio->size));
-		result = qMax(result, st::normalFont->width(formatPlayedText(audio->duration, audio->duration)));
-		result = qMax(result, st::normalFont->width(formatDurationAndSizeText(audio->duration, audio->size)));
-		return result;
-	}
-
 	int32 documentMaxStatusWidth(DocumentData *document) {
 		int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
 		if (SongData *song = document->song()) {
 			result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration)));
 			result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size)));
+		} else if (VoiceData *voice = document->voice()) {
+			result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration)));
+			result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size)));
+		} else if (document->isVideo()) {
+			result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size)));
 		} else {
 			result = qMax(result, st::normalFont->width(formatSizeText(document->size)));
 		}
@@ -3505,15 +3483,15 @@ ImagePtr HistoryPhoto::replyPreview() {
 	return _data->makeReplyPreview();
 }
 
-HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent) : HistoryFileMedia()
-, _data(App::feedVideo(video))
+HistoryVideo::HistoryVideo(DocumentData *document, const QString &caption, HistoryItem *parent) : HistoryFileMedia()
+, _data(document)
 , _thumbw(1)
 , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
 	if (!caption.isEmpty()) {
 		_caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent));
 	}
 
-	setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data));
+	setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
 
 	setStatusSize(FileStatusSizeReady);
 
@@ -3524,7 +3502,7 @@ HistoryVideo::HistoryVideo(const HistoryVideo &other) : HistoryFileMedia()
 , _data(other._data)
 , _thumbw(other._thumbw)
 , _caption(other._caption) {
-	setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data));
+	setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
 
 	setStatusSize(other._statusSize);
 }
@@ -3550,8 +3528,8 @@ void HistoryVideo::initDimensions(const HistoryItem *parent) {
 
 	_thumbw = qMax(tw, 1);
 	int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	_maxw = qMax(_thumbw, int16(minWidth));
+	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	_maxw = qMax(_thumbw, int32(minWidth));
 	_minh = qMax(th, int32(st::minPhotoSize));
 	if (bubble) {
 		_maxw += st::mediaPadding.left() + st::mediaPadding.right();
@@ -3586,8 +3564,8 @@ int32 HistoryVideo::resize(int32 width, const HistoryItem *parent) {
 	}
 
 	int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	minWidth = qMax(minWidth, videoMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
-	_width = qMax(_thumbw, int16(minWidth));
+	minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
+	_width = qMax(_thumbw, int32(minWidth));
 	_height = qMax(th, int32(st::minPhotoSize));
 	if (bubble) {
 		_width += st::mediaPadding.left() + st::mediaPadding.right();
@@ -3733,7 +3711,7 @@ void HistoryVideo::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x
 }
 
 void HistoryVideo::setStatusSize(int32 newSize) const {
-	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, 0);
+	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
 }
 
 const QString HistoryVideo::inDialogsText() const {
@@ -3764,11 +3742,11 @@ void HistoryVideo::updateStatusText(const HistoryItem *parent) const {
 }
 
 void HistoryVideo::regItem(HistoryItem *item) {
-	App::regVideoItem(_data, item);
+	App::regDocumentItem(_data, item);
 }
 
 void HistoryVideo::unregItem(HistoryItem *item) {
-	App::unregVideoItem(_data, item);
+	App::unregDocumentItem(_data, item);
 }
 
 ImagePtr HistoryVideo::replyPreview() {
@@ -3785,291 +3763,151 @@ ImagePtr HistoryVideo::replyPreview() {
 	return _data->replyPreview;
 }
 
-HistoryAudio::HistoryAudio(const MTPDaudio &audio) : HistoryFileMedia()
-, _data(App::feedAudio(audio)) {
-	setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data));
-
-	setStatusSize(FileStatusSizeReady);
+HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument *that)
+: _position(0)
+, a_progress(0., 0.)
+, _a_progress(animation(const_cast<HistoryDocument*>(that), &HistoryDocument::step_voiceProgress)) {
 }
 
-HistoryAudio::HistoryAudio(const HistoryAudio &other) : HistoryFileMedia()
-, _data(other._data) {
-	setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data));
-
-	setStatusSize(other._statusSize);
-}
-
-void HistoryAudio::initDimensions(const HistoryItem *parent) {
-	_maxw = st::msgFileMinWidth;
-
-	int32 tleft = 0, tright = 0;
-
-	tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-	tright = st::msgFileThumbPadding.left();
-	_maxw = qMax(_maxw, tleft + audioMaxStatusWidth(_data) + int(st::mediaUnreadSkip + st::mediaUnreadSize) + parent->skipBlockWidth() + st::msgPadding.right());
-
-	_maxw = qMax(tleft + st::semiboldFont->width(lang(lng_media_audio)) + tright, _maxw);
-	_maxw = qMin(_maxw, int(st::msgMaxWidth));
-
-	_height = _minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
-}
-
-void HistoryAudio::draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const {
-	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	_data->automaticLoad(parent);
-	bool loaded = _data->loaded(), displayLoading = _data->displayLoading();
-
-	bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel;
-
-	if (displayLoading) {
-		ensureAnimation(parent);
-		if (!_animation->radial.animating()) {
-			_animation->radial.start(_data->progress());
-		}
-	}
-	bool showPause = updateStatusText(parent);
-	bool radial = isRadialAnimation(ms);
-
-	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
-
-	nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
-	nametop = st::msgFileNameTop;
-	nameright = st::msgFilePadding.left();
-	statustop = st::msgFileStatusTop;
-
-	QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width));
-	p.setPen(Qt::NoPen);
-	if (selected) {
-		p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
-	} else if (isThumbAnimation(ms)) {
-		float64 over = _animation->a_thumbOver.current();
-		p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over));
-	} else {
-		bool over = textlnkDrawOver(_data->loading() ? _cancell : _savel);
-		p.setBrush(outbg ? (over ? st::msgFileOutBgOver : st::msgFileOutBg) : (over ? st::msgFileInBgOver : st::msgFileInBg));
-	}
-
-	p.setRenderHint(QPainter::HighQualityAntialiasing);
-	p.drawEllipse(inner);
-	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
-
-	if (radial) {
-		QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
-		style::color bg(outbg ? (selected ? st::msgOutBgSelected : st::msgOutBg) : (selected ? st::msgInBgSelected : st::msgInBg));
-		_animation->radial.draw(p, rinner, st::msgFileRadialLine, bg);
-	}
-
-	style::sprite icon;
-	if (showPause) {
-		icon = outbg ? (selected ? st::msgFileOutPauseSelected : st::msgFileOutPause) : (selected ? st::msgFileInPauseSelected : st::msgFileInPause);
-	} else if (radial || _data->loading()) {
-		icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel);
-	} else if (loaded) {
-		icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay);
-	} else {
-		icon = outbg ? (selected ? st::msgFileOutDownloadSelected : st::msgFileOutDownload) : (selected ? st::msgFileInDownloadSelected : st::msgFileInDownload);
-	}
-	p.drawSpriteCenter(inner, icon);
-
-	int32 namewidth = _width - nameleft - nameright;
-
-	p.setFont(st::semiboldFont);
-	p.setPen(st::black);
-	p.drawTextLeft(nameleft, nametop, _width, lang(lng_media_audio));
-
-	style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg));
-	p.setFont(st::normalFont);
-	p.setPen(status);
-	p.drawTextLeft(nameleft, statustop, _width, _statusText);
-
-	if (parent->isMediaUnread()) {
-		int32 w = st::normalFont->width(_statusText);
-		if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) {
-			p.setPen(Qt::NoPen);
-			p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
-
-			p.setRenderHint(QPainter::HighQualityAntialiasing, true);
-			p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width));
-			p.setRenderHint(QPainter::HighQualityAntialiasing, false);
-		}
+void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const {
+	if (!_playback) {
+		_playback = new HistoryDocumentVoicePlayback(that);
 	}
 }
 
-void HistoryAudio::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const {
-	if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
-
-	bool out = parent->out(), fromChannel = parent->fromChannel(), outbg = out && !fromChannel;
-	bool loaded = _data->loaded();
-
-	bool showPause = updateStatusText(parent);
-
-	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
-
-	QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top(), st::msgFileSize, st::msgFileSize, _width));
-	if ((_data->loading() || _data->status == FileUploading || !loaded) && inner.contains(x, y)) {
-		lnk = (_data->loading() || _data->status == FileUploading) ? _cancell : _savel;
-		return;
+void HistoryDocumentVoice::checkPlaybackFinished() const {
+	if (_playback && !_playback->_a_progress.animating()) {
+		delete _playback;
+		_playback = 0;
 	}
-
-	if (x >= 0 && y >= 0 && x < _width && y < _height && _data->access && !_data->loading()) {
-		lnk = _openl;
-		return;
-	}
-}
-
-const QString HistoryAudio::inDialogsText() const {
-	return lang(lng_in_dlg_audio);
-}
-
-const QString HistoryAudio::inHistoryText() const {
-	return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]");
-}
-
-void HistoryAudio::regItem(HistoryItem *item) {
-	App::regAudioItem(_data, item);
-}
-
-void HistoryAudio::unregItem(HistoryItem *item) {
-	App::unregAudioItem(_data, item);
-}
-
-void HistoryAudio::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) {
-	if (media.type() == mtpc_messageMediaAudio) {
-		App::feedAudio(media.c_messageMediaAudio().vaudio, _data);
-		if (!_data->data().isEmpty()) {
-			Local::writeAudio(mediaKey(AudioFileLocation, _data->dc, _data->id), _data->data());
-		}
-	}
-}
-
-void HistoryAudio::setStatusSize(int32 newSize, qint64 realDuration) const {
-	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration, realDuration);
-}
-
-bool HistoryAudio::updateStatusText(const HistoryItem *parent) const {
-	bool showPause = false;
-	int32 statusSize = 0, realDuration = 0;
-	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
-		statusSize = FileStatusSizeFailed;
-	} else if (_data->status == FileUploading) {
-		statusSize = _data->uploadOffset;
-	} else if (_data->loading()) {
-		statusSize = _data->loadOffset();
-	} else if (_data->loaded()) {
-		AudioMsgId playing;
-		AudioPlayerState playingState = AudioPlayerStopped;
-		int64 playingPosition = 0, playingDuration = 0;
-		int32 playingFrequency = 0;
-		if (audioPlayer()) {
-			audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
-		}
-
-		if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
-			statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
-			realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
-			showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
-		} else {
-			statusSize = FileStatusSizeLoaded;
-		}
-	} else {
-		statusSize = FileStatusSizeReady;
-	}
-	if (statusSize != _statusSize) {
-		setStatusSize(statusSize, realDuration);
-	}
-	return showPause;
 }
 
 HistoryDocument::HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia()
-, _data(document)
-, _linksavel(new DocumentSaveLink(_data))
-, _linkcancell(new DocumentCancelLink(_data))
-, _name(documentName(_data))
-, _namew(st::semiboldFont->width(_name))
-, _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
-	setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
+, _parent(0)
+, _data(document) {
+	create(!caption.isEmpty());
+	if (HistoryDocumentNamed *named = Get<HistoryDocumentNamed>()) {
+		named->_name = documentName(_data);
+		named->_namew = st::semiboldFont->width(named->_name);
+	}
+
+	setLinks(new DocumentOpenLink(_data), _data->voice() ? (ITextLink*)(new VoiceSaveLink(_data)) : new DocumentSaveLink(_data), new DocumentCancelLink(_data));
 
 	setStatusSize(FileStatusSizeReady);
 
-	if (!caption.isEmpty()) {
-		_caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent));
+	if (HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
+		captioned->_caption.setText(st::msgFont, caption + parent->skipBlock(), itemTextNoMonoOptions(parent));
 	}
 }
 
 HistoryDocument::HistoryDocument(const HistoryDocument &other) : HistoryFileMedia()
-, _data(other._data)
-, _linksavel(new DocumentSaveLink(_data))
-, _linkcancell(new DocumentCancelLink(_data))
-, _name(other._name)
-, _namew(other._namew)
-, _thumbw(other._thumbw)
-, _caption(other._caption) {
-	setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
+, _parent(0)
+, _data(other._data) {
+	const HistoryDocumentCaptioned *captioned = other.Get<HistoryDocumentCaptioned>();
+	create(captioned != 0);
+	if (HistoryDocumentNamed *named = Get<HistoryDocumentNamed>()) {
+		if (const HistoryDocumentNamed *oin = other.Get<HistoryDocumentNamed>()) {
+			named->_name = oin->_name;
+			named->_namew = oin->_namew;
+		} else {
+			named->_name = documentName(_data);
+			named->_namew = st::semiboldFont->width(named->_name);
+		}
+	}
+
+	setLinks(new DocumentOpenLink(_data), _data->voice() ? (ITextLink*)(new VoiceSaveLink(_data)) : new DocumentSaveLink(_data), new DocumentCancelLink(_data));
 
 	setStatusSize(other._statusSize);
+
+	if (captioned) {
+		Get<HistoryDocumentCaptioned>()->_caption = captioned->_caption;
+	}
+}
+
+void HistoryDocument::create(bool caption) {
+	uint64 mask;
+	if (_data->voice()) {
+		mask = HistoryDocumentVoice::Bit();
+	} else {
+		mask = HistoryDocumentNamed::Bit();
+		if (caption) {
+			mask |= HistoryDocumentCaptioned::Bit();
+		}
+		if (!_data->song() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height()) {
+			mask |= HistoryDocumentThumbed::Bit();
+		}
+	}
+	UpdateInterfaces(mask);
+	if (HistoryDocumentThumbed *thumbed = Get<HistoryDocumentThumbed>()) {
+		thumbed->_linksavel.reset(new DocumentSaveLink(_data));
+		thumbed->_linkcancell.reset(new DocumentCancelLink(_data));
+	}
 }
 
 void HistoryDocument::initDimensions(const HistoryItem *parent) {
-	if (_caption.hasSkipBlock()) {
-		_caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight());
+	_parent = parent;
+
+	HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>();
+	if (captioned && captioned->_caption.hasSkipBlock()) {
+		captioned->_caption.setSkipBlock(parent->skipBlockWidth(), parent->skipBlockHeight());
 	}
 
-	if (withThumb()) {
+	HistoryDocumentThumbed *thumbed = Get<HistoryDocumentThumbed>();
+	if (thumbed) {
 		_data->thumb->load();
 		int32 tw = _data->thumb->width(), th = _data->thumb->height();
 		if (tw > th) {
-			_thumbw = (tw * st::msgFileThumbSize) / th;
+			thumbed->_thumbw = (tw * st::msgFileThumbSize) / th;
 		} else {
-			_thumbw = st::msgFileThumbSize;
+			thumbed->_thumbw = st::msgFileThumbSize;
 		}
-	} else {
-		_thumbw = 0;
 	}
 
 	_maxw = st::msgFileMinWidth;
 
 	int32 tleft = 0, tright = 0;
-	bool wthumb = withThumb();
-	if (wthumb) {
+	if (thumbed) {
 		tleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
 		tright = st::msgFileThumbPadding.left();
 		_maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + tright);
 	} else {
 		tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
 		tright = st::msgFileThumbPadding.left();
-		_maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + parent->skipBlockWidth() + st::msgPadding.right());
+		int32 unread = _data->voice() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
+		_maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + unread + parent->skipBlockWidth() + st::msgPadding.right());
 	}
 
-	_maxw = qMax(tleft + _namew + tright, _maxw);
-	_maxw = qMin(_maxw, int(st::msgMaxWidth));
+	if (HistoryDocumentNamed *named = Get<HistoryDocumentNamed>()) {
+		_maxw = qMax(tleft + named->_namew + tright, _maxw);
+		_maxw = qMin(_maxw, int(st::msgMaxWidth));
+	}
 
-	if (wthumb) {
+	if (thumbed) {
 		_minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
 	} else {
 		_minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
 	}
 
-	if (_caption.isEmpty()) {
-		_height = _minh;
+	if (captioned) {
+		_minh += captioned->_caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
 	} else {
-		_minh += _caption.countHeight(_maxw - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
+		_height = _minh;
 	}
 }
 
 int32 HistoryDocument::resize(int32 width, const HistoryItem *parent) {
-	if (_caption.isEmpty()) {
+	HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>();
+	if (!captioned) {
 		return HistoryFileMedia::resize(width, parent);
 	}
 
 	_width = qMin(width, _maxw);
-	bool wthumb = withThumb();
-	if (wthumb) {
+	if (Get<HistoryDocumentThumbed>()) {
 		_height = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
 	} else {
 		_height = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
 	}
-	_height += _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
+	_height += captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
 
 	return _height;
 }
@@ -4094,8 +3932,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 	bool radial = isRadialAnimation(ms);
 
 	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
-	bool wthumb = withThumb();
-	if (wthumb) {
+	if (const HistoryDocumentThumbed *thumbed = Get<HistoryDocumentThumbed>()) {
 		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
 		nametop = st::msgFileThumbNameTop;
 		nameright = st::msgFileThumbPadding.left();
@@ -4104,7 +3941,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
 
 		QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width));
-		QPixmap thumb = loaded ? _data->thumb->pixSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize);
+		QPixmap thumb = loaded ? _data->thumb->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize);
 		p.drawPixmap(rthumb.topLeft(), thumb);
 		if (selected) {
 			App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
@@ -4148,11 +3985,11 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 		}
 
 		if (_data->status != FileUploadFailed) {
-			const TextLinkPtr &lnk((_data->loading() || _data->status == FileUploading) ? _linkcancell : _linksavel);
+			const TextLinkPtr &lnk((_data->loading() || _data->status == FileUploading) ? thumbed->_linkcancell : thumbed->_linksavel);
 			bool over = textlnkDrawOver(lnk);
 			p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
 			p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
-			p.drawTextLeft(nameleft, linktop, _width, _link, _linkw);
+			p.drawTextLeft(nameleft, linktop, _width, thumbed->_link, thumbed->_linkw);
 		}
 	} else {
 		nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
@@ -4189,7 +4026,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 		} else if (radial || _data->loading()) {
 			icon = outbg ? (selected ? st::msgFileOutCancelSelected : st::msgFileOutCancel) : (selected ? st::msgFileInCancelSelected : st::msgFileInCancel);
 		} else if (loaded) {
-			if (_data->song()) {
+			if (_data->song() || _data->voice()) {
 				icon = outbg ? (selected ? st::msgFileOutPlaySelected : st::msgFileOutPlay) : (selected ? st::msgFileInPlaySelected : st::msgFileInPlay);
 			} else if (_data->isImage()) {
 				icon = outbg ? (selected ? st::msgFileOutImageSelected : st::msgFileOutImage) : (selected ? st::msgFileInImageSelected : st::msgFileInImage);
@@ -4203,12 +4040,73 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 	}
 	int32 namewidth = _width - nameleft - nameright;
 
-	p.setFont(st::semiboldFont);
-	p.setPen(st::black);
-	if (namewidth < _namew) {
-		p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(_name, namewidth));
-	} else {
-		p.drawTextLeft(nameleft, nametop, _width, _name, _namew);
+	if (const HistoryDocumentVoice *voice = Get<HistoryDocumentVoice>()) {
+		const VoiceWaveform *wf = 0;
+		uchar norm_value = 0;
+		if (_data->voice()) {
+			wf = &_data->voice()->waveform;
+			if (wf->isEmpty()) {
+				wf = 0;
+				if (loaded) {
+					Local::countVoiceWaveform(_data);
+				}
+			} else if (wf->at(0) < 0) {
+				wf = 0;
+			} else {
+				norm_value = _data->voice()->wavemax;
+			}
+		}
+		float64 prg = voice->_playback ? voice->_playback->a_progress.current() : 0;
+
+		// rescale waveform by going in waveform.size * bar_count 1D grid
+		style::color active(outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive));
+		style::color inactive(outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive));
+		int32 wf_size = wf ? wf->size() : WaveformSamplesCount, availw = int32(namewidth + st::msgWaveformSkip), activew = qRound(availw * prg);
+		if (!outbg && !voice->_playback && parent->isMediaUnread()) {
+			activew = availw;
+		}
+		int32 bar_count = qMin(availw / int32(st::msgWaveformBar + st::msgWaveformSkip), wf_size);
+		uchar max_value = 0;
+		int32 max_delta = st::msgWaveformMax - st::msgWaveformMin, bottom = st::msgFilePadding.top() + st::msgWaveformMax;
+		p.setPen(Qt::NoPen);
+		for (uint32 i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
+			uchar value = wf ? wf->at(i) : 0;
+			if (sum_i + bar_count >= wf_size) { // draw bar
+				sum_i = sum_i + bar_count - wf_size;
+				if (sum_i < (bar_count + 1) / 2) {
+					if (max_value < value) max_value = value;
+				}
+				int32 bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
+
+				if (bar_x >= activew) {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
+				} else if (bar_x + st::msgWaveformBar <= activew) {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
+				} else {
+					p.fillRect(nameleft + bar_x, bottom - bar_value, activew - bar_x, st::msgWaveformMin + bar_value, active);
+					p.fillRect(nameleft + activew, bottom - bar_value, st::msgWaveformBar - (activew - bar_x), st::msgWaveformMin + bar_value, inactive);
+				}
+				bar_x += st::msgWaveformBar + st::msgWaveformSkip;
+
+				if (sum_i < (bar_count + 1) / 2) {
+					max_value = 0;
+				} else {
+					max_value = value;
+				}
+			} else {
+				if (max_value < value) max_value = value;
+
+				sum_i += bar_count;
+			}
+		}
+	} else if (const HistoryDocumentNamed *named = Get<HistoryDocumentNamed>()) {
+		p.setFont(st::semiboldFont);
+		p.setPen(st::black);
+		if (namewidth < named->_namew) {
+			p.drawTextLeft(nameleft, nametop, _width, st::semiboldFont->elided(named->_name, namewidth));
+		} else {
+			p.drawTextLeft(nameleft, nametop, _width, named->_name, named->_namew);
+		}
 	}
 
 	style::color status(outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg));
@@ -4216,9 +4114,21 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r
 	p.setPen(status);
 	p.drawTextLeft(nameleft, statustop, _width, _statusText);
 
-	if (!_caption.isEmpty()) {
+	if (parent->isMediaUnread()) {
+		int32 w = st::normalFont->width(_statusText);
+		if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= namewidth) {
+			p.setPen(Qt::NoPen);
+			p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
+
+			p.setRenderHint(QPainter::HighQualityAntialiasing, true);
+			p.drawEllipse(rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width));
+			p.setRenderHint(QPainter::HighQualityAntialiasing, false);
+		}
+	}
+
+	if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
 		p.setPen(st::black);
-		_caption.draw(p, st::msgPadding.left(), bottom, captionw);
+		captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw);
 	}
 }
 
@@ -4231,8 +4141,7 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3
 	bool showPause = updateStatusText(parent);
 
 	int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0, bottom = 0;
-	bool wthumb = withThumb();
-	if (wthumb) {
+	if (const HistoryDocumentThumbed *thumbed = Get<HistoryDocumentThumbed>()) {
 		nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
 		linktop = st::msgFileThumbLinkTop;
 		bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
@@ -4245,8 +4154,8 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3
 		}
 
 		if (_data->status != FileUploadFailed) {
-			if (rtlrect(nameleft, linktop, _linkw, st::semiboldFont->height, _width).contains(x, y)) {
-				lnk = (_data->loading() || _data->uploading()) ? _linkcancell : _linksavel;
+			if (rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, _width).contains(x, y)) {
+				lnk = (_data->loading() || _data->uploading()) ? thumbed->_linkcancell : thumbed->_linksavel;
 				return;
 			}
 		}
@@ -4261,14 +4170,14 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3
 	}
 
 	int32 height = _height;
-	if (!_caption.isEmpty()) {
+	if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
 		if (y >= bottom) {
 			bool inText = false;
-			_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right());
+			captioned->_caption.getState(lnk, inText, x - st::msgPadding.left(), y - bottom, _width - st::msgPadding.left() - st::msgPadding.right());
 			state = inText ? HistoryInTextCursorState : HistoryDefaultCursorState;
 			return;
 		}
-		height -= _caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
+		height -= captioned->_caption.countHeight(_width - st::msgPadding.left() - st::msgPadding.right()) + st::msgPadding.bottom();
 	}
 	if (x >= 0 && y >= 0 && x < _width && y < height && !_data->loading() && !_data->uploading() && _data->access) {
 		lnk = _openl;
@@ -4277,28 +4186,53 @@ void HistoryDocument::getState(TextLinkPtr &lnk, HistoryCursorState &state, int3
 }
 
 const QString HistoryDocument::inDialogsText() const {
-	return (_name.isEmpty() ? lang(lng_in_dlg_file) : _name) + (_caption.isEmpty() ? QString() : (' ' + _caption.original(0, 0xFFFF, Text::ExpandLinksNone)));
+	QString result;
+	if (Get<HistoryDocumentVoice>()) {
+		result = lang(lng_in_dlg_audio);
+	} else {
+		const HistoryDocumentNamed *named = Get<HistoryDocumentNamed>();
+		result = (!named || named->_name.isEmpty()) ? lang(lng_in_dlg_file) : named->_name;
+	}
+	if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
+		if (!captioned->_caption.isEmpty()) {
+			result.append(' ').append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksNone));
+		}
+	}
+	return result;
 }
 
 const QString HistoryDocument::inHistoryText() const {
-	return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(0, 0xFFFF, Text::ExpandLinksAll))) + qsl(" ]");
+	QString result = qsl("[ ") + lang(Get<HistoryDocumentVoice>() ? lng_in_dlg_audio : lng_in_dlg_file);
+	if (const HistoryDocumentNamed *named = Get<HistoryDocumentNamed>()) {
+		if (!named->_name.isEmpty()) {
+			result.append(qsl(" : ")).append(named->_name);
+		}
+	}
+	if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
+		if (!captioned->_caption.isEmpty()) {
+			result.append(qsl(", ")).append(captioned->_caption.original(0, 0xFFFF, Text::ExpandLinksAll));
+		}
+	}
+	return result.append(qsl(" ]"));
 }
 
 void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const {
-	HistoryFileMedia::setStatusSize(newSize, _data->size, _data->song() ? _data->song()->duration : -1, realDuration);
-
-	if (_statusSize == FileStatusSizeReady) {
-		_link = lang(lng_media_download).toUpper();
-	} else if (_statusSize == FileStatusSizeLoaded) {
-		_link = lang(lng_media_open_with).toUpper();
-	} else if (_statusSize == FileStatusSizeFailed) {
-		_link = lang(lng_media_download).toUpper();
-	} else if (_statusSize >= 0) {
-		_link = lang(lng_media_cancel).toUpper();
-	} else {
-		_link = lang(lng_media_open_with).toUpper();
+	int32 duration = _data->song() ? _data->song()->duration : (_data->voice() ? _data->voice()->duration : -1);
+	HistoryFileMedia::setStatusSize(newSize, _data->size, duration, realDuration);
+	if (const HistoryDocumentThumbed *thumbed = Get<HistoryDocumentThumbed>()) {
+		if (_statusSize == FileStatusSizeReady) {
+			thumbed->_link = lang(lng_media_download).toUpper();
+		} else if (_statusSize == FileStatusSizeLoaded) {
+			thumbed->_link = lang(lng_media_open_with).toUpper();
+		} else if (_statusSize == FileStatusSizeFailed) {
+			thumbed->_link = lang(lng_media_download).toUpper();
+		} else if (_statusSize >= 0) {
+			thumbed->_link = lang(lng_media_cancel).toUpper();
+		} else {
+			thumbed->_link = lang(lng_media_open_with).toUpper();
+		}
+		thumbed->_linkw = st::semiboldFont->width(thumbed->_link);
 	}
-	_linkw = st::semiboldFont->width(_link);
 }
 
 bool HistoryDocument::updateStatusText(const HistoryItem *parent) const {
@@ -4311,7 +4245,41 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const {
 	} else if (_data->loading()) {
 		statusSize = _data->loadOffset();
 	} else if (_data->loaded()) {
-		if (_data->song()) {
+		if (_data->voice()) {
+			AudioMsgId playing;
+			AudioPlayerState playingState = AudioPlayerStopped;
+			int64 playingPosition = 0, playingDuration = 0;
+			int32 playingFrequency = 0;
+			if (audioPlayer()) {
+				audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
+			}
+
+			if (playing.msgId == parent->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
+				if (const HistoryDocumentVoice *voice = Get<HistoryDocumentVoice>()) {
+					bool was = voice->_playback;
+					voice->ensurePlayback(this);
+					if (!was || playingPosition != voice->_playback->_position) {
+						float64 prg = playingDuration ? snap(float64(playingPosition) / playingDuration, 0., 1.) : 0.;
+						if (voice->_playback->_position < playingPosition) {
+							voice->_playback->a_progress.start(prg);
+						} else {
+							voice->_playback->a_progress = anim::fvalue(0., prg);
+						}
+						voice->_playback->_position = playingPosition;
+						voice->_playback->_a_progress.start();
+					}
+				}
+
+				statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
+				realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
+				showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
+			} else {
+				statusSize = FileStatusSizeLoaded;
+				if (const HistoryDocumentVoice *voice = Get<HistoryDocumentVoice>()) {
+					voice->checkPlaybackFinished();
+				}
+			}
+		} else if (_data->song()) {
 			SongMsgId playing;
 			AudioPlayerState playingState = AudioPlayerStopped;
 			int64 playingPosition = 0, playingDuration = 0;
@@ -4342,6 +4310,21 @@ bool HistoryDocument::updateStatusText(const HistoryItem *parent) const {
 	return showPause;
 }
 
+void HistoryDocument::step_voiceProgress(float64 ms, bool timer) {
+	if (HistoryDocumentVoice *voice = Get<HistoryDocumentVoice>()) {
+		if (voice->_playback) {
+			float64 dt = ms / (2 * AudioVoiceMsgUpdateView);
+			if (dt >= 1) {
+				voice->_playback->_a_progress.stop();
+				voice->_playback->a_progress.finish();
+			} else {
+				voice->_playback->a_progress.update(qMin(dt, 1.), anim::linear);
+			}
+			if (timer) Ui::repaintHistoryItem(_parent);
+		}
+	}
+}
+
 void HistoryDocument::regItem(HistoryItem *item) {
 	App::regDocumentItem(_data, item);
 }
@@ -4353,6 +4336,13 @@ void HistoryDocument::unregItem(HistoryItem *item) {
 void HistoryDocument::updateFrom(const MTPMessageMedia &media, HistoryItem *parent) {
 	if (media.type() == mtpc_messageMediaDocument) {
 		App::feedDocument(media.c_messageMediaDocument().vdocument, _data);
+		if (!_data->data().isEmpty()) {
+			if (_data->voice()) {
+				Local::writeAudio(_data->mediaKey(), _data->data());
+			} else {
+				Local::writeStickerImage(_data->mediaKey(), _data->data());
+			}
+		}
 	}
 }
 
@@ -4361,6 +4351,7 @@ ImagePtr HistoryDocument::replyPreview() {
 }
 
 HistoryGif::HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent) : HistoryFileMedia()
+, _parent(0)
 , _data(document)
 , _thumbw(1)
 , _thumbh(1)
@@ -4888,7 +4879,7 @@ void HistorySticker::updateFrom(const MTPMessageMedia &media, HistoryItem *paren
 	if (media.type() == mtpc_messageMediaDocument) {
 		App::feedDocument(media.c_messageMediaDocument().vdocument, _data);
 		if (!_data->data().isEmpty()) {
-			Local::writeStickerImage(mediaKey(DocumentFileLocation, _data->dc, _data->id), _data->data());
+			Local::writeStickerImage(_data->mediaKey(), _data->data());
 		}
 	}
 }
@@ -5126,7 +5117,6 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
 		_maxw = _minh = _height = 0;
 		return;
 	}
-
 	if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height);
 
 	if (!_openl && !_data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(_data->url));
@@ -5144,7 +5134,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
 		} else {
 			_asArticle = true;
 		}
-		if (_asArticle && (_data->description.isEmpty() || (title.isEmpty() && _data->siteName.isEmpty()))) {
+		if (_asArticle && _data->description.isEmpty() && title.isEmpty() && _data->siteName.isEmpty()) {
 			_asArticle = false;
 		}
 	} else {
@@ -6115,18 +6105,6 @@ void HistoryMessage::initMedia(const MTPMessageMedia *media, QString &currentTex
 			_media = new HistoryPhoto(App::feedPhoto(photo.vphoto.c_photo()), qs(photo.vcaption), this);
 		}
 	} break;
-	case mtpc_messageMediaVideo: {
-		const MTPDmessageMediaVideo &video(media->c_messageMediaVideo());
-		if (video.vvideo.type() == mtpc_video) {
-			_media = new HistoryVideo(video.vvideo.c_video(), qs(video.vcaption), this);
-		}
-	} break;
-	case mtpc_messageMediaAudio: {
-		const MTPAudio &audio(media->c_messageMediaAudio().vaudio);
-		if (audio.type() == mtpc_audio) {
-			_media = new HistoryAudio(audio.c_audio());
-		}
-	} break;
 	case mtpc_messageMediaDocument: {
 		const MTPDocument &document(media->c_messageMediaDocument().vdocument);
 		if (document.type() == mtpc_document) {
@@ -6154,6 +6132,8 @@ void HistoryMessage::initMediaFromDocument(DocumentData *doc, const QString &cap
 		_media = new HistorySticker(doc);
 	} else if (doc->isAnimation()) {
 		_media = new HistoryGif(doc, caption, this);
+	} else if (doc->isVideo()) {
+		_media = new HistoryVideo(doc, caption, this);
 	} else {
 		_media = new HistoryDocument(doc, caption, this);
 	}
@@ -6225,8 +6205,8 @@ void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const {
 	left += (!fromChannel() && out() && !Adaptive::Wide()) ? st::msgMargin.right() : st::msgMargin.left();
 	if (displayFromPhoto()) {
 		left += st::msgPhotoSkip;
-	} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) {
-		left += st::msgPhotoSkip - (hmaxwidth - hwidth);
+//	} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) {
+//		left += st::msgPhotoSkip - (hmaxwidth - hwidth);
 	}
 
 	width = hwidth - st::msgMargin.left() - st::msgMargin.right();
@@ -7554,6 +7534,8 @@ void HistoryServiceMsg::draw(Painter &p, const QRect &r, uint32 selection, uint6
 }
 
 int32 HistoryServiceMsg::resize(int32 width) {
+	int32 maxwidth = qMin(_history->width, int(st::msgMaxWidth + 2 * st::msgPhotoSkip));
+	if (width > maxwidth) width = maxwidth;
 	width -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins
 	if (width < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) width = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
 
diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h
index 955790436..0ac06d614 100644
--- a/Telegram/SourceFiles/history.h
+++ b/Telegram/SourceFiles/history.h
@@ -95,23 +95,24 @@ enum HistoryMediaType {
 	MediaTypeVideo,
 	MediaTypeGeo,
 	MediaTypeContact,
-	MediaTypeAudio,
-	MediaTypeDocument,
+	MediaTypeFile,
 	MediaTypeGif,
 	MediaTypeSticker,
 	MediaTypeImageLink,
 	MediaTypeWebPage,
+	MediaTypeMusicFile,
+	MediaTypeVoiceFile,
 
 	MediaTypeCount
 };
 
 enum MediaOverviewType {
-	OverviewPhotos,
-	OverviewVideos,
-	OverviewAudioDocuments,
-	OverviewDocuments,
-	OverviewAudios,
-	OverviewLinks,
+	OverviewPhotos     = 0,
+	OverviewVideos     = 1,
+	OverviewMusicFiles = 2,
+	OverviewFiles      = 3,
+	OverviewVoiceFiles = 4,
+	OverviewLinks      = 5,
 
 	OverviewCount
 };
@@ -120,9 +121,9 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) {
 	switch (type) {
 	case OverviewPhotos: return MTP_inputMessagesFilterPhotos();
 	case OverviewVideos: return MTP_inputMessagesFilterVideo();
-	case OverviewAudioDocuments: return MTP_inputMessagesFilterAudioDocuments();
-	case OverviewDocuments: return MTP_inputMessagesFilterDocument();
-	case OverviewAudios: return MTP_inputMessagesFilterAudio();
+	case OverviewMusicFiles: return MTP_inputMessagesFilterMusic();
+	case OverviewFiles: return MTP_inputMessagesFilterDocument();
+	case OverviewVoiceFiles: return MTP_inputMessagesFilterVoice();
 	case OverviewLinks: return MTP_inputMessagesFilterUrl();
 	default: type = OverviewCount; break;
 	}
@@ -133,8 +134,8 @@ enum SendActionType {
 	SendActionTyping,
 	SendActionRecordVideo,
 	SendActionUploadVideo,
-	SendActionRecordAudio,
-	SendActionUploadAudio,
+	SendActionRecordVoice,
+	SendActionUploadVoice,
 	SendActionUploadPhoto,
 	SendActionUploadFile,
 	SendActionChooseLocation,
@@ -1205,10 +1206,11 @@ inline MediaOverviewType mediaToOverviewType(HistoryMedia *media) {
 	switch (media->type()) {
 	case MediaTypePhoto: return OverviewPhotos;
 	case MediaTypeVideo: return OverviewVideos;
-	case MediaTypeDocument: return media->getDocument()->song() ? OverviewAudioDocuments : OverviewDocuments;
-	case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewDocuments;
-//	case MediaTypeSticker: return OverviewDocuments;
-	case MediaTypeAudio: return OverviewAudios;
+	case MediaTypeFile: return OverviewFiles;
+	case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewFiles;
+	case MediaTypeVoiceFile: return OverviewVoiceFiles;
+	case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewFiles;
+//	case MediaTypeSticker: return OverviewFiles;
 	}
 	return OverviewCount;
 }
@@ -1352,7 +1354,7 @@ private:
 class HistoryVideo : public HistoryFileMedia {
 public:
 
-	HistoryVideo(const MTPDvideo &video, const QString &caption, HistoryItem *parent);
+	HistoryVideo(DocumentData *document, const QString &caption, HistoryItem *parent);
 	HistoryVideo(const HistoryVideo &other);
 	HistoryMediaType type() const {
 		return MediaTypeVideo;
@@ -1370,7 +1372,7 @@ public:
 	const QString inDialogsText() const;
 	const QString inHistoryText() const;
 
-	VideoData *video() const {
+	DocumentData *getDocument() {
 		return _data;
 	}
 
@@ -1409,8 +1411,8 @@ protected:
 	}
 
 private:
-	VideoData *_data;
-	int16 _thumbw;
+	DocumentData *_data;
+	int32 _thumbw;
 	Text _caption;
 
 	void setStatusSize(int32 newSize) const;
@@ -1418,76 +1420,52 @@ private:
 
 };
 
-class HistoryAudio : public HistoryFileMedia {
-public:
-
-	HistoryAudio(const MTPDaudio &audio);
-	HistoryAudio(const HistoryAudio &other);
-	HistoryMediaType type() const {
-		return MediaTypeAudio;
+struct HistoryDocumentThumbed : public BasicInterface<HistoryDocumentThumbed> {
+	HistoryDocumentThumbed(Interfaces *interfaces) : _thumbw(0), _linkw(0) {
 	}
-	HistoryMedia *clone() const {
-		return new HistoryAudio(*this);
+	TextLinkPtr _linksavel, _linkcancell;
+	int32 _thumbw;
+
+	mutable int32 _linkw;
+	mutable QString _link;
+};
+struct HistoryDocumentCaptioned : public BasicInterface<HistoryDocumentCaptioned> {
+	HistoryDocumentCaptioned(Interfaces *interfaces) : _caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
 	}
-
-	void initDimensions(const HistoryItem *parent);
-
-	void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const;
-	void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const;
-
-	const QString inDialogsText() const;
-	const QString inHistoryText() const;
-
-	bool uploading() const {
-		return _data->uploading();
+	Text _caption;
+};
+struct HistoryDocumentNamed : public BasicInterface<HistoryDocumentNamed> {
+	HistoryDocumentNamed(Interfaces *interfaces) : _namew(0) {
 	}
+	QString _name;
+	int32 _namew;
+};
+class HistoryDocument;
+struct HistoryDocumentVoicePlayback {
+	HistoryDocumentVoicePlayback(const HistoryDocument *that);
 
-	AudioData *audio() {
-		return _data;
+	int32 _position;
+	anim::fvalue a_progress;
+	Animation _a_progress;
+};
+struct HistoryDocumentVoice : public BasicInterface<HistoryDocumentVoice> {
+	HistoryDocumentVoice(Interfaces *that) : _playback(0) {
 	}
-
-	void regItem(HistoryItem *item);
-	void unregItem(HistoryItem *item);
-
-	void updateFrom(const MTPMessageMedia &media, HistoryItem *parent);
-
-	bool needsBubble(const HistoryItem *parent) const {
-		return true;
+	~HistoryDocumentVoice() {
+		deleteAndMark(_playback);
 	}
-	bool customInfoLayout() const {
-		return false;
-	}
-	QMargins bubbleMargins() const {
-		return st::msgPadding;
-	}
-
-protected:
-
-	float64 dataProgress() const {
-		return _data->progress();
-	}
-	bool dataFinished() const {
-		return !_data->loading() && !_data->uploading();
-	}
-	bool dataLoaded() const {
-		return _data->loaded();
-	}
-
-private:
-	AudioData *_data;
-
-	void setStatusSize(int32 newSize, qint64 realDuration = 0) const;
-	bool updateStatusText(const HistoryItem *parent) const; // returns showPause
-
+	void ensurePlayback(const HistoryDocument *interfaces) const;
+	void checkPlaybackFinished() const;
+	mutable HistoryDocumentVoicePlayback *_playback;
 };
 
-class HistoryDocument : public HistoryFileMedia {
+class HistoryDocument : public HistoryFileMedia, public Interfaces {
 public:
 
 	HistoryDocument(DocumentData *document, const QString &caption, const HistoryItem *parent);
 	HistoryDocument(const HistoryDocument &other);
 	HistoryMediaType type() const {
-		return MediaTypeDocument;
+		return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile);
 	}
 	HistoryMedia *clone() const {
 		return new HistoryDocument(*this);
@@ -1506,10 +1484,6 @@ public:
 		return _data->uploading();
 	}
 
-	bool withThumb() const {
-		return !_data->song() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height();
-	}
-
 	DocumentData *getDocument() {
 		return _data;
 	}
@@ -1525,7 +1499,10 @@ public:
 	ImagePtr replyPreview();
 
 	QString getCaption() const {
-		return _caption.original();
+		if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
+			return captioned->_caption.original();
+		}
+		return QString();
 	}
 	bool needsBubble(const HistoryItem *parent) const {
 		return true;
@@ -1534,12 +1511,14 @@ public:
 		return false;
 	}
 	QMargins bubbleMargins() const {
-		return withThumb() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
+		return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
 	}
 	bool hideForwardedFrom() const {
 		return _data->song();
 	}
 
+	void step_voiceProgress(float64 ms, bool timer);
+
 protected:
 
 	float64 dataProgress() const {
@@ -1554,17 +1533,9 @@ protected:
 
 private:
 
+	void create(bool caption);
+	const HistoryItem *_parent;
 	DocumentData *_data;
-	TextLinkPtr _linksavel, _linkcancell;
-
-	QString _name;
-	int32 _namew;
-	int32 _thumbw;
-
-	mutable int32 _linkw;
-	mutable QString _link;
-
-	Text _caption;
 
 	void setStatusSize(int32 newSize, qint64 realDuration = 0) const;
 	bool updateStatusText(const HistoryItem *parent) const; // returns showPause
@@ -2237,7 +2208,20 @@ inline int32 newMessageFlags(PeerData *p) {
 	return p->isSelf() ? 0 : (((p->isChat() || (p->isUser() && !p->asUser()->botInfo)) ? MTPDmessage::flag_unread : 0) | MTPDmessage::flag_out);
 }
 inline int32 newForwardedFlags(PeerData *p, int32 from, HistoryMessage *msg) {
-	return newMessageFlags(p) | (from ? MTPDmessage::flag_from_id : 0) | (msg->via() ? MTPDmessage::flag_via_bot_id : 0) | (!p->isChannel() && msg->getMedia() && (msg->getMedia()->type() == MediaTypeAudio/* || msg->getMedia()->type() == MediaTypeVideo*/) ? MTPDmessage::flag_media_unread : 0);
+	int32 result = newMessageFlags(p) | (from ? MTPDmessage::flag_from_id : 0);
+	if (msg->via()) {
+		result |= MTPDmessage::flag_via_bot_id;
+	}
+	if (!p->isChannel()) {
+		if (HistoryMedia *media = msg->getMedia()) {
+			if (media->type() == MediaTypeVoiceFile) {
+				result |= MTPDmessage::flag_media_unread;
+//			} else if (media->type() == MediaTypeVideo) {
+//				result |= MTPDmessage::flag_media_unread;
+			}
+		}
+	}
+	return result;
 }
 
 class HistoryServiceMsg : public HistoryItem {
diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp
index b4103258c..ad23e890b 100644
--- a/Telegram/SourceFiles/historywidget.cpp
+++ b/Telegram/SourceFiles/historywidget.cpp
@@ -864,10 +864,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 	_contextMenuLnk = textlnkOver();
 	HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
 	PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
-    VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-    AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
     DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
+	bool lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideo() : false;
+	bool lnkIsAudio = lnkDocument ? lnkDocument->document()->voice() : false;
+	if (lnkPhoto || lnkDocument) {
 		if (isUponSelected > 0) {
 			_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
 		}
@@ -879,17 +879,17 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 			_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
 			_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
 		} else {
-			if ((lnkVideo && lnkVideo->video()->loading()) || (lnkAudio && lnkAudio->audio()->loading()) || (lnkDocument && lnkDocument->document()->loading())) {
+			if (lnkDocument && lnkDocument->document()->loading()) {
 				_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
 			} else {
 				if (lnkDocument && lnkDocument->document()->loaded() && lnkDocument->document()->isGifv()) {
 					_menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true);
 				}
-				if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
+				if (lnkDocument && !lnkDocument->document()->already(true).isEmpty()) {
 					_menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
 				}
-				_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_file)), this, SLOT(openContextFile()))->setEnabled(true);
-				_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_file)), this, SLOT(saveContextFile()))->setEnabled(true);
+				_menu->addAction(lang(lnkIsVideo ? lng_context_open_video : (lnkIsAudio ? lng_context_open_audio : lng_context_open_file)), this, SLOT(openContextFile()))->setEnabled(true);
+				_menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : lng_context_save_file)), this, SLOT(saveContextFile()))->setEnabled(true);
 			}
 		}
 		if (isUponSelected > 1) {
@@ -1069,11 +1069,7 @@ void HistoryInner::copyContextImage() {
 }
 
 void HistoryInner::cancelContextDownload() {
-	if (VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data())) {
-		lnkVideo->video()->cancel();
-	} else if (AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data())) {
-		lnkAudio->audio()->cancel();
-	} else if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
+	if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
 		lnkDocument->document()->cancel();
 	} else if (HistoryItem *item = App::contextItem()) {
 		if (HistoryMedia *media = item->getMedia()) {
@@ -1086,11 +1082,7 @@ void HistoryInner::cancelContextDownload() {
 
 void HistoryInner::showContextInFolder() {
 	QString already;
-	if (VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data())) {
-		already = lnkVideo->video()->already(true);
-	} else if (AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data())) {
-		already = lnkAudio->audio()->already(true);
-	} else if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
+	if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
 		already = lnkDocument->document()->already(true);
 	} else if (HistoryItem *item = App::contextItem()) {
 		if (HistoryMedia *media = item->getMedia()) {
@@ -1105,21 +1097,13 @@ void HistoryInner::showContextInFolder() {
 void HistoryInner::openContextFile() {
 	HistoryItem *was = App::hoveredLinkItem();
 	App::hoveredLinkItem(App::contextItem());
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-    AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
     DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) VideoOpenLink(lnkVideo->video()).onClick(Qt::LeftButton);
-	if (lnkAudio) AudioOpenLink(lnkAudio->audio()).onClick(Qt::LeftButton);
 	if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton);
 	App::hoveredLinkItem(was);
 }
 
 void HistoryInner::saveContextFile() {
-	if (VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data())) {
-		VideoSaveLink::doSave(lnkVideo->video(), true);
-	} else if (AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data())) {
-		AudioSaveLink::doSave(lnkAudio->audio(), true);
-	} else if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
+	if (DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data())) {
 		DocumentSaveLink::doSave(lnkDocument->document(), true);
 	} else if (HistoryItem *item = App::contextItem()) {
 		if (HistoryMedia *media = item->getMedia()) {
@@ -2720,8 +2704,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
 	connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout()));
 	if (audioCapture()) {
 		connect(audioCapture(), SIGNAL(onError()), this, SLOT(onRecordError()));
-		connect(audioCapture(), SIGNAL(onUpdate(qint16,qint32)), this, SLOT(onRecordUpdate(qint16,qint32)));
-		connect(audioCapture(), SIGNAL(onDone(QByteArray,qint32)), this, SLOT(onRecordDone(QByteArray,qint32)));
+		connect(audioCapture(), SIGNAL(onUpdate(quint16,qint32)), this, SLOT(onRecordUpdate(quint16,qint32)));
+		connect(audioCapture(), SIGNAL(onDone(QByteArray,VoiceWaveform,qint32)), this, SLOT(onRecordDone(QByteArray,VoiceWaveform,qint32)));
 	}
 
 	_updateHistoryItems.setSingleShot(true);
@@ -3005,8 +2989,8 @@ void HistoryWidget::updateSendAction(History *history, SendActionType type, int3
 		case SendActionTyping: action = MTP_sendMessageTypingAction(); break;
 		case SendActionRecordVideo: action = MTP_sendMessageRecordVideoAction(); break;
 		case SendActionUploadVideo: action = MTP_sendMessageUploadVideoAction(MTP_int(progress)); break;
-		case SendActionRecordAudio: action = MTP_sendMessageRecordAudioAction(); break;
-		case SendActionUploadAudio: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break;
+		case SendActionRecordVoice: action = MTP_sendMessageRecordAudioAction(); break;
+		case SendActionUploadVoice: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break;
 		case SendActionUploadPhoto: action = MTP_sendMessageUploadPhotoAction(MTP_int(progress)); break;
 		case SendActionUploadFile: action = MTP_sendMessageUploadDocumentAction(MTP_int(progress)); break;
 		case SendActionChooseLocation: action = MTP_sendMessageGeoLocationAction(); break;
@@ -3055,16 +3039,16 @@ void HistoryWidget::onRecordError() {
 	stopRecording(false);
 }
 
-void HistoryWidget::onRecordDone(QByteArray result, qint32 samples) {
+void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) {
 	if (!_peer) return;
 
 	App::wnd()->activateWindow();
 	int32 duration = samples / AudioVoiceMsgFrequency;
-	_fileLoader.addTask(new FileLoadTask(result, duration, FileLoadTo(_peer->id, _broadcast.checked(), replyToId())));
+	_fileLoader.addTask(new FileLoadTask(result, duration, waveform, FileLoadTo(_peer->id, _broadcast.checked(), replyToId())));
 	cancelReply(lastForceReplyReplied());
 }
 
-void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) {
+void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
 	if (!_recording) {
 		return;
 	}
@@ -3077,7 +3061,7 @@ void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) {
 	}
 	updateField();
 	if (_peer && (!_peer->isChannel() || _peer->isMegagroup() || !_peer->asChannel()->canPublish() || (!_peer->asChannel()->isBroadcast() && !_broadcast.checked()))) {
-		updateSendAction(_history, SendActionRecordAudio);
+		updateSendAction(_history, SendActionRecordVoice);
 	}
 }
 
@@ -4241,9 +4225,9 @@ void HistoryWidget::firstLoadMessages() {
 	}
 
 	if (loadImportant) {
-		_firstLoadRequest = MTP::send(MTPchannels_GetImportantHistory(from->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
+		_firstLoadRequest = MTP::send(MTPchannels_GetImportantHistory(from->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 	} else {
-		_firstLoadRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
+		_firstLoadRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 	}
 }
 
@@ -4265,9 +4249,9 @@ void HistoryWidget::loadMessages() {
 	int32 offset = 0, loadCount = offset_id ? MessagesPerPage : MessagesFirstLoad;
 
 	if (loadImportant) {
-		_preloadRequest = MTP::send(MTPchannels_GetImportantHistory(from->peer->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
+		_preloadRequest = MTP::send(MTPchannels_GetImportantHistory(from->peer->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 	} else {
-		_preloadRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
+		_preloadRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 	}
 }
 
@@ -4295,9 +4279,9 @@ void HistoryWidget::loadMessagesDown() {
 	}
 
 	if (loadImportant) {
-		_preloadDownRequest = MTP::send(MTPchannels_GetImportantHistory(from->peer->asChannel()->inputChannel, MTP_int(offset_id + 1), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
+		_preloadDownRequest = MTP::send(MTPchannels_GetImportantHistory(from->peer->asChannel()->inputChannel, MTP_int(offset_id + 1), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 	} else {
-		_preloadDownRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id + 1), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
+		_preloadDownRequest = MTP::send(MTPmessages_GetHistory(from->peer->input, MTP_int(offset_id + 1), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from->peer), rpcFail(&HistoryWidget::messagesFailed));
 	}
 }
 
@@ -4355,9 +4339,9 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
 	}
 
 	if (loadImportant) {
-		_delayedShowAtRequest = MTP::send(MTPchannels_GetImportantHistory(from->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
+		_delayedShowAtRequest = MTP::send(MTPchannels_GetImportantHistory(from->asChannel()->inputChannel, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 	} else {
-		_delayedShowAtRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
+		_delayedShowAtRequest = MTP::send(MTPmessages_GetHistory(from->input, MTP_int(offset_id), MTP_int(0), MTP_int(offset), MTP_int(loadCount), MTP_int(0), MTP_int(0)), rpcDone(&HistoryWidget::messagesReceived, from), rpcFail(&HistoryWidget::messagesFailed));
 	}
 }
 
@@ -4807,7 +4791,7 @@ void HistoryWidget::onDocumentSelect() {
 void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) {
 	if (!_history) return;
 
-	if (_peer && (_peer->isChannel() && !_peer->asChannel()->canPublish())) return;
+	if (_peer && !_canSendMessages) return;
 
 	_attachDrag = getDragState(e->mimeData());
 	updateDragAreas();
@@ -4887,7 +4871,7 @@ void HistoryWidget::stopRecording(bool send) {
 	_recording = false;
 	_recordingSamples = 0;
 	if (_peer && (!_peer->isChannel() || _peer->isMegagroup() || !_peer->asChannel()->canPublish() || (!_peer->asChannel()->isBroadcast() && !_broadcast.checked()))) {
-		updateSendAction(_history, SendActionRecordAudio, -1);
+		updateSendAction(_history, SendActionRecordVoice, -1);
 	}
 
 	updateControlsVisibility();
@@ -5151,7 +5135,7 @@ void HistoryWidget::onPhotoDrop(const QMimeData *data) {
 void HistoryWidget::onDocumentDrop(const QMimeData *data) {
 	if (!_history) return;
 
-	if (_peer && (_peer->isChannel() && !_peer->asChannel()->canPublish())) return;
+	if (_peer && !_canSendMessages) return;
 
 	QStringList files = getMediasFromMime(data);
 	if (files.isEmpty()) return;
@@ -5161,7 +5145,7 @@ void HistoryWidget::onDocumentDrop(const QMimeData *data) {
 
 void HistoryWidget::onFilesDrop(const QMimeData *data) {
 
-	if (_peer && (_peer->isChannel() && !_peer->asChannel()->canPublish())) return;
+	if (_peer && !_canSendMessages) return;
 
 	QStringList files = getMediasFromMime(data);
 	if (files.isEmpty()) {
@@ -5522,13 +5506,10 @@ void HistoryWidget::confirmSendFile(const FileLoadResultPtr &file, bool ctrlShif
 	connect(App::uploader(), SIGNAL(photoReady(const FullMsgId&, const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(documentReady(const FullMsgId&, const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&, const MTPInputFile&, const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&, const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection);
-	connect(App::uploader(), SIGNAL(audioReady(const FullMsgId&, const MTPInputFile&)), this, SLOT(onAudioUploaded(const FullMsgId&, const MTPInputFile&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection);
-	connect(App::uploader(), SIGNAL(audioProgress(const FullMsgId&)), this, SLOT(onAudioProgress(const FullMsgId&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection);
 	connect(App::uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection);
-	connect(App::uploader(), SIGNAL(audioFailed(const FullMsgId&)), this, SLOT(onAudioFailed(const FullMsgId&)), Qt::UniqueConnection);
 
 	App::uploader()->upload(newId, file);
 
@@ -5552,7 +5533,7 @@ void HistoryWidget::confirmSendFile(const FileLoadResultPtr &file, bool ctrlShif
 		if (!h->peer->isChannel()) {
 			flags |= MTPDmessage::flag_media_unread;
 		}
-		h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaAudio(file->audio), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread);
+		h->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(fromChannelName ? 0 : MTP::authedId()), peerToMTP(file->to.peer), MTPPeer(), MTPint(), MTPint(), MTP_int(file->to.replyTo), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaDocument(file->document, MTP_string(file->caption)), MTPnullMarkup, MTPnullEntities, MTP_int(1)), NewMessageUnread);
 	}
 
 	if (_peer && file->to.peer == _peer->id) {
@@ -5628,7 +5609,9 @@ namespace {
 		} else if (document->type == StickerDocument && document->sticker()) {
 			attributes.push_back(MTP_documentAttributeSticker(MTP_string(document->sticker()->alt), document->sticker()->set));
 		} else if (document->type == SongDocument && document->song()) {
-			attributes.push_back(MTP_documentAttributeAudio(MTP_int(document->song()->duration), MTP_string(document->song()->title), MTP_string(document->song()->performer)));
+			attributes.push_back(MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_title | MTPDdocumentAttributeAudio::flag_performer), MTP_int(document->song()->duration), MTP_string(document->song()->title), MTP_string(document->song()->performer), MTPstring()));
+		} else if (document->type == VoiceDocument && document->voice()) {
+			attributes.push_back(MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_voice | MTPDdocumentAttributeAudio::flag_waveform), MTP_int(document->voice()->duration), MTPstring(), MTPstring(), MTP_string(documentWaveformEncode5bit(document->voice()->waveform))));
 		}
 		return MTP_vector<MTPDocumentAttribute>(attributes);
 	}
@@ -5684,33 +5667,6 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, const MTPInp
 	}
 }
 
-void HistoryWidget::onAudioUploaded(const FullMsgId &newId, const MTPInputFile &file) {
-	if (!MTP::authedId()) return;
-	HistoryMessage *item = dynamic_cast<HistoryMessage*>(App::histItemById(newId));
-	if (item) {
-		AudioData *audio = 0;
-		if (HistoryAudio *media = dynamic_cast<HistoryAudio*>(item->getMedia())) {
-			audio = media->audio();
-		}
-		if (audio) {
-			uint64 randomId = MTP::nonce<uint64>();
-			App::historyRegRandom(randomId, newId);
-			History *hist = item->history();
-			MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0;
-			int32 sendFlags = 0;
-			if (replyTo) {
-				sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id;
-			}
-
-			bool fromChannelName = hist->peer->isChannel() && !hist->peer->isMegagroup() && hist->peer->asChannel()->canPublish() && item->fromChannel();
-			if (fromChannelName) {
-				sendFlags |= MTPmessages_SendMedia::flag_broadcast;
-			}
-			hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedAudio(file, MTP_int(audio->duration), MTP_string(audio->mime)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
-		}
-	}
-}
-
 void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
 	if (!MTP::authedId()) return;
 	if (HistoryItem *item = App::histItemById(newId)) {
@@ -5728,18 +5684,7 @@ void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
 		HistoryMedia *media = item->getMedia();
 		DocumentData *doc = media ? media->getDocument() : 0;
 		if (!item->fromChannel()) {
-			updateSendAction(item->history(), SendActionUploadFile, doc ? doc->uploadOffset : 0);
-		}
-		Ui::repaintHistoryItem(item);
-	}
-}
-
-void HistoryWidget::onAudioProgress(const FullMsgId &newId) {
-	if (!MTP::authedId()) return;
-	if (HistoryItem *item = App::histItemById(newId)) {
-		AudioData *audio = (item->getMedia() && item->getMedia()->type() == MediaTypeAudio) ? static_cast<HistoryAudio*>(item->getMedia())->audio() : 0;
-		if (!item->fromChannel()) {
-			updateSendAction(item->history(), SendActionUploadAudio, audio ? audio->uploadOffset : 0);
+			updateSendAction(item->history(), (doc && doc->voice()) ? SendActionUploadVoice : SendActionUploadFile, doc ? doc->uploadOffset : 0);
 		}
 		Ui::repaintHistoryItem(item);
 	}
@@ -5760,19 +5705,10 @@ void HistoryWidget::onDocumentFailed(const FullMsgId &newId) {
 	if (!MTP::authedId()) return;
 	HistoryItem *item = App::histItemById(newId);
 	if (item) {
+		HistoryMedia *media = item->getMedia();
+		DocumentData *doc = media ? media->getDocument() : 0;
 		if (!item->fromChannel()) {
-			updateSendAction(item->history(), SendActionUploadFile, -1);
-		}
-		Ui::repaintHistoryItem(item);
-	}
-}
-
-void HistoryWidget::onAudioFailed(const FullMsgId &newId) {
-	if (!MTP::authedId()) return;
-	HistoryItem *item = App::histItemById(newId);
-	if (item) {
-		if (!item->fromChannel()) {
-			updateSendAction(item->history(), SendActionUploadAudio, -1);
+			updateSendAction(item->history(), (doc && doc->voice()) ? SendActionUploadVoice : SendActionUploadFile, -1);
 		}
 		Ui::repaintHistoryItem(item);
 	}
@@ -7194,7 +7130,7 @@ void HistoryWidget::drawRecording(Painter &p) {
 	p.setPen(Qt::NoPen);
 	p.setBrush(st::recordSignalColor->b);
 	p.setRenderHint(QPainter::HighQualityAntialiasing);
-	float64 delta = qMin(float64(a_recordingLevel.current()) * 3 * M_PI / 0x7fff, 1.);
+	float64 delta = qMin(float64(a_recordingLevel.current()) / 0x4000, 1.);
 	int32 d = 2 * qRound(st::recordSignalMin + (delta * (st::recordSignalMax - st::recordSignalMin)));
 	p.drawEllipse(_attachPhoto.x() + (_attachEmoji.width() - d) / 2, _attachPhoto.y() + (_attachPhoto.height() - d) / 2, d, d);
 	p.setRenderHint(QPainter::HighQualityAntialiasing, false);
diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h
index eda8743a1..fcf4c5377 100644
--- a/Telegram/SourceFiles/historywidget.h
+++ b/Telegram/SourceFiles/historywidget.h
@@ -610,15 +610,12 @@ public slots:
 	void onPhotoUploaded(const FullMsgId &msgId, const MTPInputFile &file);
 	void onDocumentUploaded(const FullMsgId &msgId, const MTPInputFile &file);
 	void onThumbDocumentUploaded(const FullMsgId &msgId, const MTPInputFile &file, const MTPInputFile &thumb);
-	void onAudioUploaded(const FullMsgId &msgId, const MTPInputFile &file);
 
 	void onPhotoProgress(const FullMsgId &msgId);
 	void onDocumentProgress(const FullMsgId &msgId);
-	void onAudioProgress(const FullMsgId &msgId);
 
 	void onPhotoFailed(const FullMsgId &msgId);
 	void onDocumentFailed(const FullMsgId &msgId);
-	void onAudioFailed(const FullMsgId &msgId);
 
 	void onReportSpamClicked();
 	void onReportSpamSure();
@@ -683,8 +680,8 @@ public slots:
 	void updateField();
 
 	void onRecordError();
-	void onRecordDone(QByteArray result, qint32 samples);
-	void onRecordUpdate(qint16 level, qint32 samples);
+	void onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples);
+	void onRecordUpdate(quint16 level, qint32 samples);
 
 	void onUpdateHistoryItems();
 
diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp
index cf3c71c76..20b5ad04c 100644
--- a/Telegram/SourceFiles/layout.cpp
+++ b/Telegram/SourceFiles/layout.cpp
@@ -385,11 +385,11 @@ void LayoutOverviewPhoto::getState(TextLinkPtr &link, HistoryCursorState &cursor
 	}
 }
 
-LayoutOverviewVideo::LayoutOverviewVideo(VideoData *video, HistoryItem *parent) : LayoutAbstractFileItem(0, parent)
+LayoutOverviewVideo::LayoutOverviewVideo(DocumentData *video, HistoryItem *parent) : LayoutAbstractFileItem(0, parent)
 , _data(video)
-, _duration(formatDurationText(_data->duration))
+, _duration(formatDurationText(_data->duration()))
 , _thumbLoaded(false) {
-	setLinks(new VideoOpenLink(_data), new VideoSaveLink(_data), new VideoCancelLink(_data));
+	setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
 }
 
 void LayoutOverviewVideo::initDimensions() {
@@ -550,23 +550,25 @@ void LayoutOverviewVideo::updateStatusText() const {
 	}
 }
 
-LayoutOverviewAudio::LayoutOverviewAudio(AudioData *audio, HistoryItem *parent) : LayoutAbstractFileItem(OverviewItemInfo::Bit(), parent)
-, _data(audio)
-, _namel(new AudioOpenLink(_data)) {
-	setLinks(new AudioOpenLink(_data), new AudioOpenLink(_data), new AudioCancelLink(_data));
+LayoutOverviewVoice::LayoutOverviewVoice(DocumentData *voice, HistoryItem *parent) : LayoutAbstractFileItem(OverviewItemInfo::Bit(), parent)
+, _data(voice)
+, _namel(new DocumentOpenLink(_data)) {
+	t_assert(_data->voice() != 0);
+
+	setLinks(new DocumentOpenLink(_data), new DocumentOpenLink(_data), new DocumentCancelLink(_data));
 	updateName();
 	QString d = textcmdLink(1, textRichPrepare(langDateTime(date(_data->date))));
 	TextParseOptions opts = { TextParseRichText, 0, 0, Qt::LayoutDirectionAuto };
-	_details.setText(st::normalFont, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->duration)), opts);
+	_details.setText(st::normalFont, lng_date_and_duration(lt_date, d, lt_duration, formatDurationText(_data->voice()->duration)), opts);
 	_details.setLink(1, TextLinkPtr(new MessageLink(parent)));
 }
 
-void LayoutOverviewAudio::initDimensions() {
+void LayoutOverviewVoice::initDimensions() {
 	_maxw = st::profileMaxWidth;
 	_minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() + st::lineWidth;
 }
 
-void LayoutOverviewAudio::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const {
+void LayoutOverviewVoice::paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const {
 	bool selected = (selection == FullSelection);
 
 	_data->automaticLoad(_parent);
@@ -666,7 +668,7 @@ void LayoutOverviewAudio::paint(Painter &p, const QRect &clip, uint32 selection,
 	}
 }
 
-void LayoutOverviewAudio::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const {
+void LayoutOverviewVoice::getState(TextLinkPtr &link, HistoryCursorState &cursor, int32 x, int32 y) const {
 	bool loaded = _data->loaded();
 
 	bool showPause = updateStatusText();
@@ -696,7 +698,7 @@ void LayoutOverviewAudio::getState(TextLinkPtr &link, HistoryCursorState &cursor
 	}
 }
 
-void LayoutOverviewAudio::updateName() const {
+void LayoutOverviewVoice::updateName() const {
 	int32 version = 0;
 	if (HistoryForwarded *fwd = _parent->toHistoryForwarded()) {
 		_name.setText(st::semiboldFont, lang(lng_forwarded_from) + ' ' + App::peerName(fwd->fromForwarded()), _textNameOptions);
@@ -708,7 +710,7 @@ void LayoutOverviewAudio::updateName() const {
 	_nameVersion = version;
 }
 
-bool LayoutOverviewAudio::updateStatusText() const {
+bool LayoutOverviewVoice::updateStatusText() const {
 	bool showPause = false;
 	int32 statusSize = 0, realDuration = 0;
 	if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
@@ -733,7 +735,7 @@ bool LayoutOverviewAudio::updateStatusText() const {
 		statusSize = FileStatusSizeReady;
 	}
 	if (statusSize != _statusSize) {
-		setStatusSize(statusSize, _data->size, _data->duration, realDuration);
+		setStatusSize(statusSize, _data->size, _data->voice()->duration, realDuration);
 	}
 	return showPause;
 }
diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h
index 39f9ecd14..9cd624127 100644
--- a/Telegram/SourceFiles/layout.h
+++ b/Telegram/SourceFiles/layout.h
@@ -317,7 +317,7 @@ private:
 
 class LayoutOverviewVideo : public LayoutAbstractFileItem {
 public:
-	LayoutOverviewVideo(VideoData *photo, HistoryItem *parent);
+	LayoutOverviewVideo(DocumentData *video, HistoryItem *parent);
 
 	virtual void initDimensions();
 	virtual int32 resizeGetHeight(int32 width);
@@ -339,7 +339,7 @@ protected:
 	}
 
 private:
-	VideoData *_data;
+	DocumentData *_data;
 
 	QString _duration;
 	mutable QPixmap _pix;
@@ -349,9 +349,9 @@ private:
 
 };
 
-class LayoutOverviewAudio : public LayoutAbstractFileItem {
+class LayoutOverviewVoice : public LayoutAbstractFileItem {
 public:
-	LayoutOverviewAudio(AudioData *audio, HistoryItem *parent);
+	LayoutOverviewVoice(DocumentData *voice, HistoryItem *parent);
 
 	virtual void initDimensions();
 	virtual void paint(Painter &p, const QRect &clip, uint32 selection, const PaintContext *context) const;
@@ -372,7 +372,7 @@ protected:
 	}
 
 private:
-	AudioData *_data;
+	DocumentData *_data;
 	TextLinkPtr _namel;
 
 	mutable Text _name, _details;
diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp
index d8137a832..49d0cfaf2 100644
--- a/Telegram/SourceFiles/localimageloader.cpp
+++ b/Telegram/SourceFiles/localimageloader.cpp
@@ -198,10 +198,11 @@ FileLoadTask::FileLoadTask(const QImage &image, PrepareMediaType type, const Fil
 , _result(0) {
 }
 
-FileLoadTask::FileLoadTask(const QByteArray &audio, int32 duration, const FileLoadTo &to) : _id(MTP::nonce<uint64>())
+FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to) : _id(MTP::nonce<uint64>())
 , _to(to)
-, _content(audio)
+, _content(voice)
 , _duration(duration)
+, _waveform(waveform)
 , _type(PrepareAudio)
 , _confirm(FileLoadNoForceConfirm)
 , _result(0) {
@@ -220,7 +221,7 @@ void FileLoadTask::process() {
 	QString thumbname = "thumb.jpg";
 	QByteArray thumbdata;
 
-	bool animated = false;
+	bool animated = false, song = false, gif = false, voice = (_type == PrepareAudio);
 	QImage fullimage = _image;
 
 	if (!_filepath.isEmpty()) {
@@ -232,30 +233,32 @@ void FileLoadTask::process() {
 		filesize = info.size();
 		filemime = mimeTypeForFile(info).name();
 		filename = info.fileName();
-		if (filesize <= MaxUploadPhotoSize && _type != PrepareAudio) {
+		if (filesize <= MaxUploadPhotoSize && !voice) {
 			bool opaque = (filemime != stickerMime);
 			fullimage = App::readImage(_filepath, 0, opaque, &animated);
 		}
 	} else if (!_content.isEmpty()) {
 		filesize = _content.size();
-		MimeType mimeType = mimeTypeForData(_content);
-		filemime = mimeType.name();
-		if (filesize <= MaxUploadPhotoSize && _type != PrepareAudio) {
-			bool opaque = (filemime != stickerMime);
-			fullimage = App::readImage(_content, 0, opaque, &animated);
-		}
-		if (filemime == "image/jpeg") {
-			filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true);
-		} else if (_type == PrepareAudio) {
+		if (voice) {
 			filename = filedialogDefaultName(qsl("audio"), qsl(".ogg"), QString(), true);
 			filemime = "audio/ogg";
 		} else {
-			QString ext;
-			QStringList patterns = mimeType.globPatterns();
-			if (!patterns.isEmpty()) {
-				ext = patterns.front().replace('*', QString());
+			MimeType mimeType = mimeTypeForData(_content);
+			filemime = mimeType.name();
+			if (filesize <= MaxUploadPhotoSize && !voice) {
+				bool opaque = (filemime != stickerMime);
+				fullimage = App::readImage(_content, 0, opaque, &animated);
+			}
+			if (filemime == "image/jpeg") {
+				filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true);
+			} else {
+				QString ext;
+				QStringList patterns = mimeType.globPatterns();
+				if (!patterns.isEmpty()) {
+					ext = patterns.front().replace('*', QString());
+				}
+				filename = filedialogDefaultName(qsl("file"), ext, QString(), true);
 			}
-			filename = filedialogDefaultName(qsl("file"), ext, QString(), true);
 		}
 	} else if (!_image.isNull()) {
 		_image = QImage();
@@ -292,10 +295,8 @@ void FileLoadTask::process() {
 	MTPPhotoSize thumbSize(MTP_photoSizeEmpty(MTP_string("")));
 	MTPPhoto photo(MTP_photoEmpty(MTP_long(0)));
 	MTPDocument document(MTP_documentEmpty(MTP_long(0)));
-	MTPAudio audio(MTP_audioEmpty(MTP_long(0)));
 
-	bool song = false, gif = false;
-	if (_type != PrepareAudio) {
+	if (!voice) {
 		if (filemime == qstr("audio/mp3") || filemime == qstr("audio/m4a") || filemime == qstr("audio/aac") || filemime == qstr("audio/ogg") || filemime == qstr("audio/flac") ||
 			filename.endsWith(qstr(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) ||
 			filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive) ||
@@ -358,7 +359,7 @@ void FileLoadTask::process() {
 		}
 	}
 
-	if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif) {
+	if (!fullimage.isNull() && fullimage.width() > 0 && !song && !gif && !voice) {
 		int32 w = fullimage.width(), h = fullimage.height();
 		attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));
 
@@ -408,8 +409,10 @@ void FileLoadTask::process() {
 		}
 	}
 
-	if (_type == PrepareAudio) {
-		audio = MTP_audio(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_int(_duration), MTP_string(filemime), MTP_int(filesize), MTP_int(MTP::maindc()));
+	if (voice) {
+		attributes[0] = MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_voice | MTPDdocumentAttributeAudio::flag_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_string(documentWaveformEncode5bit(_waveform)));
+		attributes.resize(1);
+		document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
 	} else {
 		document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
 		if (photo.type() == mtpc_photoEmpty) {
@@ -431,7 +434,6 @@ void FileLoadTask::process() {
 	_result->thumb = thumb;
 
 	_result->photo = photo;
-	_result->audio = audio;
 	_result->document = document;
 	_result->photoThumbs = photoThumbs;
 }
diff --git a/Telegram/SourceFiles/localimageloader.h b/Telegram/SourceFiles/localimageloader.h
index 6078820af..51edd7310 100644
--- a/Telegram/SourceFiles/localimageloader.h
+++ b/Telegram/SourceFiles/localimageloader.h
@@ -52,8 +52,8 @@ typedef QList<ToPrepareMedia> ToPrepareMedias;
 
 typedef QMap<int32, QByteArray> UploadFileParts;
 struct ReadyLocalMedia {
-	ReadyLocalMedia(PrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const MTPAudio &audio, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) :
-		replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), audio(audio), photoThumbs(photoThumbs), broadcast(broadcast), ctrlShiftEnter(ctrlShiftEnter) {
+	ReadyLocalMedia(PrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) :
+		replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), photoThumbs(photoThumbs), broadcast(broadcast), ctrlShiftEnter(ctrlShiftEnter) {
 		if (!jpeg.isEmpty()) {
 			int32 size = jpeg.size();
 			for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) {
@@ -74,7 +74,6 @@ struct ReadyLocalMedia {
 
 	MTPPhoto photo;
 	MTPDocument document;
-	MTPAudio audio;
 	PreparedPhotoThumbs photoThumbs;
 	UploadFileParts parts;
 	QByteArray jpeg_md5;
@@ -114,7 +113,7 @@ public:
 	TaskId addTask(TaskPtr task);
 	void addTasks(const TasksList &tasks);
 	void cancelTask(TaskId id); // this task finish() won't be called
-	
+
 	TaskId addTask(Task *task) {
 		return addTask(TaskPtr(task));
 	}
@@ -203,7 +202,6 @@ struct FileLoadResult {
 	QPixmap thumb;
 
 	MTPPhoto photo;
-	MTPAudio audio;
 	MTPDocument document;
 
 	PreparedPhotoThumbs photoThumbs;
@@ -248,7 +246,7 @@ public:
 	FileLoadTask(const QString &filepath, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm);
 	FileLoadTask(const QByteArray &content, PrepareMediaType type, const FileLoadTo &to);
 	FileLoadTask(const QImage &image, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, const QString &originalText = QString());
-	FileLoadTask(const QByteArray &audio, int32 duration, const FileLoadTo &to);
+	FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to);
 
 	uint64 fileid() const {
 		return _id;
@@ -265,6 +263,7 @@ protected:
 	QImage _image;
 	QByteArray _content;
 	int32 _duration;
+	VoiceWaveform _waveform;
 	PrepareMediaType _type;
 	FileLoadForceConfirmType _confirm;
 	QString _originalText;
diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp
index 915bd3535..da64f2cb9 100644
--- a/Telegram/SourceFiles/localstorage.cpp
+++ b/Telegram/SourceFiles/localstorage.cpp
@@ -2785,6 +2785,77 @@ namespace Local {
 		return _storageWebFilesSize;
 	}
 
+	class CountWaveformTask : public Task {
+	public:
+		CountWaveformTask(DocumentData *doc)
+			: _doc(doc)
+			, _loc(doc->location(true))
+			, _data(doc->data())
+			, _wavemax(0) {
+			if (_data.isEmpty() && !_loc.accessEnable()) {
+				_doc = 0;
+			}
+		}
+		void process() {
+			if (!_doc) return;
+
+			_waveform = audioCountWaveform(_loc, _data);
+			uchar wavemax = 0;
+			for (int32 i = 0, l = _waveform.size(); i < l; ++i) {
+				uchar waveat = _waveform.at(i);
+				if (wavemax < waveat) wavemax = waveat;
+			}
+			_wavemax = wavemax;
+		}
+		void finish() {
+			if (VoiceData *voice = _doc ? _doc->voice() : 0) {
+				if (!_waveform.isEmpty()) {
+					voice->waveform = _waveform;
+					voice->wavemax = _wavemax;
+				}
+				if (voice->waveform.isEmpty()) {
+					voice->waveform.resize(1);
+					voice->waveform[0] = -2;
+					voice->wavemax = 0;
+				} else if (voice->waveform[0] < 0) {
+					voice->waveform[0] = -2;
+					voice->wavemax = 0;
+				}
+				const DocumentItems &items(App::documentItems());
+				DocumentItems::const_iterator i = items.constFind(_doc);
+				if (i != items.cend()) {
+					for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
+						Ui::repaintHistoryItem(j.key());
+					}
+				}
+			}
+		}
+		virtual ~CountWaveformTask() {
+			if (_data.isEmpty() && _doc) {
+				_loc.accessDisable();
+			}
+		}
+
+	protected:
+		DocumentData *_doc;
+		FileLocation _loc;
+		QByteArray _data;
+		VoiceWaveform _waveform;
+		char _wavemax;
+
+	};
+
+	void countVoiceWaveform(DocumentData *document) {
+		if (VoiceData *voice = document->voice()) {
+			if (_localLoader) {
+				voice->waveform.resize(1 + sizeof(TaskId));
+				voice->waveform[0] = -1; // counting
+				TaskId taskId = _localLoader->addTask(new CountWaveformTask(document));
+				memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId));
+			}
+		}
+	}
+
 	void cancelTask(TaskId id) {
 		if (_localLoader) {
 			_localLoader->cancelTask(id);
diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h
index 7bc16304b..607ec9d0f 100644
--- a/Telegram/SourceFiles/localstorage.h
+++ b/Telegram/SourceFiles/localstorage.h
@@ -144,6 +144,8 @@ namespace Local {
 	int32 hasWebFiles();
 	qint64 storageWebFilesSize();
 
+	void countVoiceWaveform(DocumentData *document);
+
 	void cancelTask(TaskId id);
 
 	void writeStickers();
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 76efb6a1c..c632b510d 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -1084,6 +1084,8 @@ bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) {
 
 	QString text = lang(lng_failed_add_participant);
 	if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
+	} else if (error.type() == "USER_PRIVACY_RESTRICTED") {
+		text = lang(lng_cant_invite_privacy);
 	} else if (error.type() == "USER_NOT_MUTUAL_CONTACT") { // trying to return user who does not have me in contacts
 		text = lang(lng_failed_add_not_mutual);
 	} else if (error.type() == "USER_ALREADY_PARTICIPANT" && user->botInfo) {
@@ -1100,6 +1102,8 @@ bool MainWidget::addParticipantsFail(ChannelData *channel, const RPCError &error
 
 	QString text = lang(lng_failed_add_participant);
 	if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
+	} else if (error.type() == "USER_PRIVACY_RESTRICTED") {
+		text = lang(lng_cant_invite_privacy_channel);
 	} else if (error.type() == "USER_NOT_MUTUAL_CONTACT") { // trying to return user who does not have me in contacts
 		text = lang(channel->isMegagroup() ? lng_failed_add_not_mutual : lng_failed_add_not_mutual_channel);
 	} else if (error.type() == "PEER_FLOOD") {
@@ -1124,9 +1128,9 @@ bool MainWidget::kickParticipantFail(ChatData *chat, const RPCError &error) {
 
 void MainWidget::checkPeerHistory(PeerData *peer) {
 	if (peer->isChannel() && !peer->isMegagroup()) {
-		MTP::send(MTPchannels_GetImportantHistory(peer->asChannel()->inputChannel, MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::checkedHistory, peer));
+		MTP::send(MTPchannels_GetImportantHistory(peer->asChannel()->inputChannel, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::checkedHistory, peer));
 	} else {
-		MTP::send(MTPmessages_GetHistory(peer->input, MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::checkedHistory, peer));
+		MTP::send(MTPmessages_GetHistory(peer->input, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::checkedHistory, peer));
 	}
 }
 
@@ -1479,9 +1483,9 @@ void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) {
 					switch (i) {
 					case OverviewPhotos: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaPhotos, lang(lng_media_type_photos))), SIGNAL(clicked()), this, SLOT(onPhotosSelect())); break;
 					case OverviewVideos: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaVideos, lang(lng_media_type_videos))), SIGNAL(clicked()), this, SLOT(onVideosSelect())); break;
-					case OverviewAudioDocuments: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaSongs, lang(lng_media_type_songs))), SIGNAL(clicked()), this, SLOT(onSongsSelect())); break;
-					case OverviewDocuments: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaDocuments, lang(lng_media_type_files))), SIGNAL(clicked()), this, SLOT(onDocumentsSelect())); break;
-					case OverviewAudios: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaAudios, lang(lng_media_type_audios))), SIGNAL(clicked()), this, SLOT(onAudiosSelect())); break;
+					case OverviewMusicFiles: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaSongs, lang(lng_media_type_songs))), SIGNAL(clicked()), this, SLOT(onSongsSelect())); break;
+					case OverviewFiles: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaDocuments, lang(lng_media_type_files))), SIGNAL(clicked()), this, SLOT(onDocumentsSelect())); break;
+					case OverviewVoiceFiles: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaAudios, lang(lng_media_type_audios))), SIGNAL(clicked()), this, SLOT(onAudiosSelect())); break;
 					case OverviewLinks: connect(_mediaType.addButton(new IconedButton(this, st::dropdownMediaLinks, lang(lng_media_type_links))), SIGNAL(clicked()), this, SLOT(onLinksSelect())); break;
 					}
 				}
@@ -1647,24 +1651,6 @@ void MainWidget::messagesAffected(PeerData *peer, const MTPmessages_AffectedMess
 	}
 }
 
-void MainWidget::videoLoadProgress(FileLoader *loader) {
-	mtpFileLoader *l = loader ? loader->mtpLoader() : 0;
-	if (!l) return;
-
-	VideoData *video = App::video(l->objId());
-	if (video->loaded()) {
-		video->performActionOnLoad();
-	}
-
-	const VideoItems &items(App::videoItems());
-	VideoItems::const_iterator i = items.constFind(video);
-	if (i != items.cend()) {
-		for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
-			Ui::repaintHistoryItem(j.key());
-		}
-	}
-}
-
 void MainWidget::loadFailed(mtpFileLoader *loader, bool started, const char *retrySlot) {
 	failedObjId = loader->objId();
 	failedFileName = loader->fileName();
@@ -1691,42 +1677,6 @@ void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId) {
 	Ui::showPeerHistory(peerId, showAtMsgId);
 }
 
-void MainWidget::videoLoadFailed(FileLoader *loader, bool started) {
-	mtpFileLoader *l = loader ? loader->mtpLoader() : 0;
-	if (!l) return;
-
-	loadFailed(l, started, SLOT(videoLoadRetry()));
-	VideoData *video = App::video(l->objId());
-	if (video) {
-		if (video->loading()) video->cancel();
-		video->status = FileDownloadFailed;
-	}
-}
-
-void MainWidget::videoLoadRetry() {
-	Ui::hideLayer();
-	VideoData *video = App::video(failedObjId);
-	if (video) video->save(failedFileName);
-}
-
-void MainWidget::audioLoadProgress(FileLoader *loader) {
-	mtpFileLoader *l = loader ? loader->mtpLoader() : 0;
-	if (!l) return;
-
-	AudioData *audio = App::audio(l->objId());
-	if (audio->loaded()) {
-		audio->performActionOnLoad();
-	}
-
-	const AudioItems &items(App::audioItems());
-	AudioItems::const_iterator i = items.constFind(audio);
-	if (i != items.cend()) {
-		for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
-			Ui::repaintHistoryItem(j.key());
-		}
-	}
-}
-
 void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
 	AudioMsgId playing;
 	AudioPlayerState state = AudioPlayerStopped;
@@ -1734,7 +1684,7 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
 	if (playing == audioId && state == AudioPlayerStoppedAtStart) {
 		audioPlayer()->clearStoppedAtStart(audioId);
 
-		AudioData *audio = audioId.audio;
+		DocumentData *audio = audioId.audio;
 		QString already = audio->already(true);
 		if (already.isEmpty() && !audio->data().isEmpty()) {
 			bool mp3 = (audio->mime == qstr("audio/mp3"));
@@ -1746,7 +1696,7 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
 						f.close();
 						already = filename;
 						audio->setLocation(FileLocation(StorageFilePartial, filename));
-						Local::writeFileLocation(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), audio->dc, audio->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
+						Local::writeFileLocation(mediaKey(AudioFileLocation, audio->dc, audio->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
 					}
 				}
 			}
@@ -1794,7 +1744,7 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) {
 						f.close();
 						already = filename;
 						document->setLocation(FileLocation(StorageFilePartial, filename));
-						Local::writeFileLocation(mediaKey(mtpToLocationType(mtpc_inputDocumentFileLocation), document->dc, document->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
+						Local::writeFileLocation(mediaKey(DocumentFileLocation, document->dc, document->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
 					}
 				}
 			}
@@ -1831,24 +1781,6 @@ void MainWidget::hidePlayer() {
 	}
 }
 
-void MainWidget::audioLoadFailed(FileLoader *loader, bool started) {
-	mtpFileLoader *l = loader ? loader->mtpLoader() : 0;
-	if (!l) return;
-
-	loadFailed(l, started, SLOT(audioLoadRetry()));
-	AudioData *audio = App::audio(l->objId());
-	if (audio) {
-		if (audio->loading()) audio->cancel();
-		audio->status = FileDownloadFailed;
-	}
-}
-
-void MainWidget::audioLoadRetry() {
-	Ui::hideLayer();
-	AudioData *audio = App::audio(failedObjId);
-	if (audio) audio->save(failedFileName);
-}
-
 void MainWidget::documentLoadProgress(FileLoader *loader) {
 	mtpFileLoader *l = loader ? loader->mtpLoader() : 0;
 	if (!l) return;
@@ -1915,17 +1847,9 @@ void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) {
 	//Ui::repaintInlineItem();
 }
 
-void MainWidget::audioMarkRead(AudioData *data) {
-	const AudioItems &items(App::audioItems());
-	AudioItems::const_iterator i = items.constFind(data);
-	if (i != items.cend()) {
-		mediaMarkRead(i.value());
-	}
-}
-
-void MainWidget::videoMarkRead(VideoData *data) {
-	const VideoItems &items(App::videoItems());
-	VideoItems::const_iterator i = items.constFind(data);
+void MainWidget::mediaMarkRead(DocumentData *data) {
+	const DocumentItems &items(App::documentItems());
+	DocumentItems::const_iterator i = items.constFind(data);
 	if (i != items.cend()) {
 		mediaMarkRead(i.value());
 	}
@@ -2428,7 +2352,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
 	if (overview && overview->peer() == peer) {
 		if (overview->type() != type) {
 			overview->switchType(type);
-		} else if (type == OverviewAudioDocuments) { // hack for player
+		} else if (type == OverviewMusicFiles) { // hack for player
 			showBackFromStack();
 		}
 		return;
@@ -2872,17 +2796,17 @@ void MainWidget::onVideosSelect() {
 }
 
 void MainWidget::onSongsSelect() {
-	if (overview) overview->switchType(OverviewAudioDocuments);
+	if (overview) overview->switchType(OverviewMusicFiles);
 	_mediaType.hideStart();
 }
 
 void MainWidget::onDocumentsSelect() {
-	if (overview) overview->switchType(OverviewDocuments);
+	if (overview) overview->switchType(OverviewFiles);
 	_mediaType.hideStart();
 }
 
 void MainWidget::onAudiosSelect() {
-	if (overview) overview->switchType(OverviewAudios);
+	if (overview) overview->switchType(OverviewVoiceFiles);
 	_mediaType.hideStart();
 }
 
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index ffb357d81..31a8989df 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -375,8 +375,7 @@ public:
 	void cancelForwarding();
 	void finishForwarding(History *hist, bool broadcast); // send them
 
-	void audioMarkRead(AudioData *data);
-	void videoMarkRead(VideoData *data);
+	void mediaMarkRead(DocumentData *data);
 	void mediaMarkRead(const HistoryItemsMap &items);
 
 	void webPageUpdated(WebPageData *page);
@@ -445,12 +444,6 @@ public slots:
 
 	void webPagesUpdate();
 
-	void videoLoadProgress(FileLoader *loader);
-	void videoLoadFailed(FileLoader *loader, bool started);
-	void videoLoadRetry();
-	void audioLoadProgress(FileLoader *loader);
-	void audioLoadFailed(FileLoader *loader, bool started);
-	void audioLoadRetry();
 	void audioPlayProgress(const AudioMsgId &audioId);
 	void documentLoadProgress(FileLoader *loader);
 	void documentLoadFailed(FileLoader *loader, bool started);
diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp
index 84af93dba..a73603e01 100644
--- a/Telegram/SourceFiles/mediaview.cpp
+++ b/Telegram/SourceFiles/mediaview.cpp
@@ -363,7 +363,7 @@ void MediaView::updateControls() {
 		_dateNav = myrtlrect(st::mvTextLeft, height() - st::mvTextTop, st::mvFont->width(_dateText), st::mvFont->height);
 	}
 	updateHeader();
-	if (_photo || (_history && (_overview == OverviewPhotos || _overview == OverviewDocuments))) {
+	if (_photo || (_history && (_overview == OverviewPhotos || _overview == OverviewFiles))) {
 		_leftNavVisible = (_index > 0) || (_index == 0 && (
 			(!_msgmigrated && _history && _history->overview[_overview].size() < _history->overviewCount(_overview)) ||
 			(_msgmigrated && _migrated && _migrated->overview[_overview].size() < _migrated->overviewCount(_overview)) ||
@@ -865,7 +865,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
 	_canForward = _msgid > 0;
 	_canDelete = context ? context->canDelete() : false;
 	if (_history) {
-		_overview = OverviewDocuments;
+		_overview = OverviewFiles;
 		findCurrent();
 	}
 	displayDocument(doc, context);
@@ -1486,7 +1486,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
 }
 
 void MediaView::moveToNext(int32 delta) {
-	if (_index < 0 || (_history && _overview != OverviewPhotos && _overview != OverviewDocuments) || (_overview == OverviewCount && !_user)) {
+	if (_index < 0 || (_history && _overview != OverviewPhotos && _overview != OverviewFiles) || (_overview == OverviewCount && !_user)) {
 		return;
 	}
 	if (_msgmigrated && !_history->overviewLoaded(_overview)) {
@@ -1515,7 +1515,7 @@ void MediaView::moveToNext(int32 delta) {
 				if (HistoryMedia *media = item->getMedia()) {
 					switch (media->type()) {
 					case MediaTypePhoto: displayPhoto(static_cast<HistoryPhoto*>(item->getMedia())->photo(), item); preloadData(delta); break;
-					case MediaTypeDocument:
+					case MediaTypeFile:
 					case MediaTypeGif:
 					case MediaTypeSticker: displayDocument(media->getDocument(), item); preloadData(delta); break;
 					}
@@ -1562,7 +1562,7 @@ void MediaView::preloadData(int32 delta) {
 				if (HistoryMedia *media = item->getMedia()) {
 					switch (media->type()) {
 					case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->forget(); break;
-					case MediaTypeDocument:
+					case MediaTypeFile:
 					case MediaTypeGif:
 					case MediaTypeSticker: media->getDocument()->forget(); break;
 					}
@@ -1587,7 +1587,7 @@ void MediaView::preloadData(int32 delta) {
 					if (HistoryMedia *media = item->getMedia()) {
 						switch (media->type()) {
 						case MediaTypePhoto: static_cast<HistoryPhoto*>(media)->photo()->download(); break;
-						case MediaTypeDocument:
+						case MediaTypeFile:
 						case MediaTypeGif: {
 							DocumentData *doc = media->getDocument();
 							doc->thumb->load();
diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.cpp b/Telegram/SourceFiles/mtproto/mtpConnection.cpp
index b1bfac554..dcde71a52 100644
--- a/Telegram/SourceFiles/mtproto/mtpConnection.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpConnection.cpp
@@ -405,33 +405,44 @@ namespace {
 		return mayBeBadKey;
 	}
 
-	mtpBuffer _handleTcpResponse(mtpPrime *packet, uint32 size) {
-		if (size < 4 || size * sizeof(mtpPrime) > MTPPacketSizeMax) {
-			LOG(("TCP Error: bad packet size %1").arg(size * sizeof(mtpPrime)));
+	uint32 _tcpPacketSize(const char *packet) { // must have at least 4 bytes readable
+		uint32 result = (packet[0] > 0) ? packet[0] : 0;
+		if (result == 0x7f) {
+			const uchar *bytes = reinterpret_cast<const uchar*>(packet);
+			result = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]);
+			return (result << 2) + 4;
+		}
+		return (result << 2) + 1;
+	}
+
+	mtpBuffer _handleTcpResponse(const char *packet, uint32 length) {
+		if (length < 5 || length > MTPPacketSizeMax) {
+			LOG(("TCP Error: bad packet size %1").arg(length));
 			return mtpBuffer(1, -500);
 		}
-        if (packet[0] != int32(size * sizeof(mtpPrime))) {
+		int32 size = packet[0], len = length - 1;
+		if (size == 0x7f) {
+			const uchar *bytes = reinterpret_cast<const uchar*>(packet);
+			size = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]);
+			len -= 3;
+		}
+		if (size * sizeof(mtpPrime) != len) {
 			LOG(("TCP Error: bad packet header"));
-			TCP_LOG(("TCP Error: bad packet header, packet: %1").arg(Logs::mb(packet, size * sizeof(mtpPrime)).str()));
+			TCP_LOG(("TCP Error: bad packet header, packet: %1").arg(Logs::mb(packet, length).str()));
 			return mtpBuffer(1, -500);
 		}
-		if (packet[size - 1] != hashCrc32(packet, (size - 1) * sizeof(mtpPrime))) {
-			LOG(("TCP Error: bad packet checksum"));
-			TCP_LOG(("TCP Error: bad packet checksum, packet: %1").arg(Logs::mb(packet, size * sizeof(mtpPrime)).str()));
-			return mtpBuffer(1, -500);
-		}
-		TCP_LOG(("TCP Info: packet received, num = %1, size = %2").arg(packet[1]).arg(size * sizeof(mtpPrime)));
-		if (size == 4) {
-			if (packet[2] == -429) {
+		TCP_LOG(("TCP Info: packet received, size = %1").arg(size * sizeof(mtpPrime)));
+		if (size == 1) {
+			if (packet[0] == -429) {
 				LOG(("Protocol Error: -429 flood code returned!"));
 			} else {
-				LOG(("TCP Error: error packet received, code = %1").arg(packet[2]));
+				LOG(("TCP Error: error packet received, code = %1").arg(packet[0]));
 			}
-			return mtpBuffer(1, packet[2]);
+			return mtpBuffer(1, packet[0]);
 		}
 
-		mtpBuffer data(size - 3);
-		memcpy(data.data(), packet + 2, (size - 3) * sizeof(mtpPrime));
+		mtpBuffer data(size);
+		memcpy(data.data(), packet + (length - len), size * sizeof(mtpPrime));
 
 		return data;
 	}
@@ -557,7 +568,7 @@ void MTPabstractTcpConnection::socketRead() {
 			if (packetLeft) {
 				packetLeft -= bytes;
 				if (!packetLeft) {
-					socketPacket((mtpPrime*)(currentPos - packetRead), packetRead >> 2);
+					socketPacket(currentPos - packetRead, packetRead);
 					currentPos = (char*)shortBuffer;
 					packetRead = packetLeft = 0;
 					readingToShort = true;
@@ -568,14 +579,14 @@ void MTPabstractTcpConnection::socketRead() {
 			} else {
 				bool move = false;
 				while (packetRead >= 4) {
-					uint32 packetSize = *(uint32*)(currentPos - packetRead);
-					if (packetSize < 16 || packetSize > MTPPacketSizeMax || (packetSize & 0x03)) {
+					uint32 packetSize = _tcpPacketSize(currentPos - packetRead);
+					if (packetSize < 5 || packetSize > MTPPacketSizeMax) {
 						LOG(("TCP Error: packet size = %1").arg(packetSize));
 						emit error();
 						return;
 					}
 					if (packetRead >= packetSize) {
-						socketPacket((mtpPrime*)(currentPos - packetRead), packetSize >> 2);
+						socketPacket(currentPos - packetRead, packetSize);
 						packetRead -= packetSize;
 						packetLeft = 0;
 						move = true;
@@ -704,15 +715,41 @@ void MTPautoConnection::sendData(mtpBuffer &buffer) {
 	}
 }
 
+uint32 FourCharsToUInt(char ch1, char ch2, char ch3, char ch4) {
+	char ch[4] = { ch1, ch2, ch3, ch4 };
+	return *reinterpret_cast<uint32*>(ch);
+}
+
 void MTPautoConnection::tcpSend(mtpBuffer &buffer) {
-	uint32 size = buffer.size(), len = size * 4;
+	if (!packetNum) {
+		char nonce[64];
+		uint32 *first = reinterpret_cast<uint32*>(nonce), *second = first + 1;
+		uint32 g1 = FourCharsToUInt('P', 'O', 'S', 'T'), g2 = FourCharsToUInt('G', 'E', 'T', ' '), g3 = FourCharsToUInt('H', 'E', 'A', 'D');
+		uint32 first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU;
+		uint32 second1 = 0;
+		do {
+			memset_rand(nonce, sizeof(nonce));
+		} while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || *second == second1 || nonce[0] == 0xef);
+		sock.write(nonce, sizeof(nonce));
+	}
+	++packetNum;
 
-	buffer[0] = len;
-	buffer[1] = packetNum++;
-	buffer[size - 1] = hashCrc32(&buffer[0], len - 4);
-	TCP_LOG(("TCP Info: write %1 packet %2 bytes").arg(packetNum).arg(len));
+	uint32 size = buffer.size() - 3, len = size * 4;
+	char *data = reinterpret_cast<char*>(&buffer[0]);
+	if (size < 0x7f) {
+		data[7] = char(size);
+		TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 1));
 
-	sock.write((const char*)&buffer[0], len);
+		sock.write(data + 7, len + 1);
+	} else {
+		data[4] = 0x7f;
+		reinterpret_cast<uchar*>(data)[5] = uchar(size & 0xFF);
+		reinterpret_cast<uchar*>(data)[6] = uchar((size >> 8) & 0xFF);
+		reinterpret_cast<uchar*>(data)[7] = uchar((size >> 16) & 0xFF);
+		TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 4));
+
+		sock.write(data + 4, len + 4);
+	}
 }
 
 void MTPautoConnection::httpSend(mtpBuffer &buffer) {
@@ -831,10 +868,10 @@ void MTPautoConnection::requestFinished(QNetworkReply *reply) {
 	}
 }
 
-void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
+void MTPautoConnection::socketPacket(const char *packet, uint32 length) {
 	if (status == FinishedWork) return;
 
-	mtpBuffer data = _handleTcpResponse(packet, size);
+	mtpBuffer data = _handleTcpResponse(packet, length);
 	if (data.size() == 1) {
 		if (status == WaitingBoth) {
 			status = WaitingHttp;
@@ -984,14 +1021,35 @@ void MTPtcpConnection::sendData(mtpBuffer &buffer) {
 		return;
 	}
 
-	uint32 size = buffer.size(), len = size * 4;
+	if (!packetNum) {
+		char nonce[64];
+		uint32 *first = reinterpret_cast<uint32*>(nonce), *second = first + 1;
+		uint32 g1 = FourCharsToUInt('P', 'O', 'S', 'T'), g2 = FourCharsToUInt('G', 'E', 'T', ' '), g3 = FourCharsToUInt('H', 'E', 'A', 'D');
+		uint32 first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU;
+		uint32 second1 = 0;
+		do {
+			memset_rand(nonce, sizeof(nonce));
+		} while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || *second == second1 || nonce[0] == 0xef);
+		sock.write(nonce, sizeof(nonce));
+	}
+	++packetNum;
 
-	buffer[0] = len;
-	buffer[1] = packetNum++;
-	buffer[size - 1] = hashCrc32(&buffer[0], len - 4);
-	TCP_LOG(("TCP Info: write %1 packet %2 bytes %3").arg(packetNum).arg(len).arg(Logs::mb(&buffer[0], len).str()));
+	uint32 size = buffer.size() - 3, len = size * 4;
+	char *data = reinterpret_cast<char*>(&buffer[0]);
+	if (size < 0x7f) {
+		data[7] = char(size);
+		TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 1));
 
-	sock.write((const char*)&buffer[0], len);
+		sock.write(data + 7, len + 1);
+	} else {
+		data[4] = 0x7f;
+		reinterpret_cast<uchar*>(data)[5] = uchar(size & 0xFF);
+		reinterpret_cast<uchar*>(data)[6] = uchar((size >> 8) & 0xFF);
+		reinterpret_cast<uchar*>(data)[7] = uchar((size >> 16) & 0xFF);
+		TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 4));
+
+		sock.write(data + 4, len + 4);
+	}
 }
 
 void MTPtcpConnection::disconnectFromServer() {
@@ -1011,10 +1069,10 @@ void MTPtcpConnection::connectToServer(const QString &addr, int32 port, int32 fl
 	sock.connectToHost(QHostAddress(_addr), _port);
 }
 
-void MTPtcpConnection::socketPacket(mtpPrime *packet, uint32 size) {
+void MTPtcpConnection::socketPacket(const char *packet, uint32 length) {
 	if (status == FinishedWork) return;
 
-	mtpBuffer data = _handleTcpResponse(packet, size);
+	mtpBuffer data = _handleTcpResponse(packet, length);
 	if (data.size() == 1) {
 		bool mayBeBadKey = (data[0] == -410) && _sentEncrypted;
 		emit error(mayBeBadKey);
diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.h b/Telegram/SourceFiles/mtproto/mtpConnection.h
index 74d281ae4..5674fc401 100644
--- a/Telegram/SourceFiles/mtproto/mtpConnection.h
+++ b/Telegram/SourceFiles/mtproto/mtpConnection.h
@@ -168,7 +168,7 @@ protected:
 	char *currentPos;
 	mtpBuffer longBuffer;
 	mtpPrime shortBuffer[MTPShortBufferSize];
-	virtual void socketPacket(mtpPrime *packet, uint32 packetSize) = 0;
+	virtual void socketPacket(const char *packet, uint32 length) = 0;
 
 };
 
@@ -203,7 +203,7 @@ public slots:
 
 protected:
 
-	void socketPacket(mtpPrime *packet, uint32 packetSize);
+	void socketPacket(const char *packet, uint32 length);
 
 private:
 
@@ -261,7 +261,7 @@ public slots:
 
 protected:
 
-	void socketPacket(mtpPrime *packet, uint32 packetSize);
+	void socketPacket(const char *packet, uint32 length);
 
 private:
 
@@ -285,7 +285,7 @@ class MTPhttpConnection : public MTPabstractConnection {
 public:
 
 	MTPhttpConnection(QThread *thread);
-	
+
 	void sendData(mtpBuffer &buffer);
 	void disconnectFromServer();
 	void connectToServer(const QString &addr, int32 port, int32 flags);
@@ -441,7 +441,7 @@ private:
 
 	// if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found
 	bool requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 serverTime, uint64 serverSalt);
-	
+
 	// remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked
 	void requestsAcked(const QVector<MTPlong> &ids, bool byResponse = false);
 
@@ -491,7 +491,7 @@ private:
 		MTPlong retry_id;
 
 		int32 g;
-		
+
 		uchar aesKey[32], aesIV[32];
 		uint32 auth_key[64];
 		MTPlong auth_key_hash;
diff --git a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h
index 60d05a96a..b6dab0b00 100644
--- a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h
+++ b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h
@@ -368,7 +368,7 @@ static const mtpTypeId mtpLayers[] = {
 	mtpTypeId(mtpc_invokeWithLayer18),
 };
 static const uint32 mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
-static const mtpPrime mtpCurrentLayer = 45;
+static const mtpPrime mtpCurrentLayer = 47;
 
 template <typename bareT>
 class MTPBoxed : public bareT {
diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
index e7c7f20c9..04a7b68eb 100644
--- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp
@@ -387,8 +387,8 @@ bool mtpFileLoader::loadPart() {
 		limit = DownloadPartSize;
 	} else {
 		switch (_locationType) {
-		case VideoFileLocation: loc = MTP_inputVideoFileLocation(MTP_long(_id), MTP_long(_access)); break;
-		case AudioFileLocation: loc = MTP_inputAudioFileLocation(MTP_long(_id), MTP_long(_access)); break;
+		case VideoFileLocation:
+		case AudioFileLocation:
 		case DocumentFileLocation: loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_access)); break;
 		default: cancel(true); return false; break;
 		}
diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.h b/Telegram/SourceFiles/mtproto/mtpFileLoader.h
index d9f06aec1..9446c979b 100644
--- a/Telegram/SourceFiles/mtproto/mtpFileLoader.h
+++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.h
@@ -30,23 +30,6 @@ enum LocationType {
 	AudioFileLocation    = 0x74dc404d, // mtpc_inputAudioFileLocation
 	VideoFileLocation    = 0x3d0364ec, // mtpc_inputVideoFileLocation
 };
-inline LocationType mtpToLocationType(mtpTypeId type) {
-	switch (type) {
-		case mtpc_inputDocumentFileLocation: return DocumentFileLocation;
-		case mtpc_inputAudioFileLocation: return AudioFileLocation;
-		case mtpc_inputVideoFileLocation: return VideoFileLocation;
-		default: return UnknownFileLocation;
-	}
-}
-inline mtpTypeId mtpFromLocationType(LocationType type) {
-	switch (type) {
-		case DocumentFileLocation: return mtpc_inputDocumentFileLocation;
-		case AudioFileLocation: return mtpc_inputAudioFileLocation;
-		case VideoFileLocation: return mtpc_inputVideoFileLocation;
-		case UnknownFileLocation:
-		default: return 0;
-	}
-}
 
 enum StorageFileType {
 	StorageFileUnknown = 0xaa963b05, // mtpc_storage_fileUnknown
diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp
index aa213c7ed..e2231a54a 100644
--- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp
+++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp
@@ -642,85 +642,6 @@ void _serialize_inputMediaContact(MTPStringLogger &to, int32 stage, int32 lev, T
 	}
 }
 
-void _serialize_inputMediaUploadedVideo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputMediaUploadedVideo");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  w: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  h: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_inputMediaUploadedThumbVideo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputMediaUploadedThumbVideo");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  w: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  h: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 6: to.add("  caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_inputMediaVideo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputMediaVideo");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_inputMediaUploadedAudio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputMediaUploadedAudio");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_inputMediaAudio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputMediaAudio");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_inputMediaUploadedDocument(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -867,24 +788,6 @@ void _serialize_inputPhoto(MTPStringLogger &to, int32 stage, int32 lev, Types &t
 	}
 }
 
-void _serialize_inputVideoEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	to.add("{ inputVideoEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
-}
-
-void _serialize_inputVideo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputVideo");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_inputFileLocation(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -900,20 +803,6 @@ void _serialize_inputFileLocation(MTPStringLogger &to, int32 stage, int32 lev, T
 	}
 }
 
-void _serialize_inputVideoFileLocation(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputVideoFileLocation");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_inputEncryptedFileLocation(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -928,20 +817,6 @@ void _serialize_inputEncryptedFileLocation(MTPStringLogger &to, int32 stage, int
 	}
 }
 
-void _serialize_inputAudioFileLocation(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputAudioFileLocation");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_inputDocumentFileLocation(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -1279,14 +1154,15 @@ void _serialize_channel(MTPStringLogger &to, int32 stage, int32 lev, Types &type
 	case 7: to.add("  verified: "); ++stages.back(); if (flag & MTPDchannel::flag_verified) { to.add("YES [ BY BIT 7 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 7 IN FIELD flags ]"); } break;
 	case 8: to.add("  megagroup: "); ++stages.back(); if (flag & MTPDchannel::flag_megagroup) { to.add("YES [ BY BIT 8 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 8 IN FIELD flags ]"); } break;
 	case 9: to.add("  restricted: "); ++stages.back(); if (flag & MTPDchannel::flag_restricted) { to.add("YES [ BY BIT 9 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break;
-	case 10: to.add("  id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 11: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 12: to.add("  title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 13: to.add("  username: "); ++stages.back(); if (flag & MTPDchannel::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
-	case 14: to.add("  photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 15: to.add("  date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 16: to.add("  version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 17: to.add("  restriction_reason: "); ++stages.back(); if (flag & MTPDchannel::flag_restriction_reason) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break;
+	case 10: to.add("  invites_enabled: "); ++stages.back(); if (flag & MTPDchannel::flag_invites_enabled) { to.add("YES [ BY BIT 10 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 10 IN FIELD flags ]"); } break;
+	case 11: to.add("  id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 12: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 13: to.add("  title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 14: to.add("  username: "); ++stages.back(); if (flag & MTPDchannel::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
+	case 15: to.add("  photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 16: to.add("  date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 17: to.add("  version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 18: to.add("  restriction_reason: "); ++stages.back(); if (flag & MTPDchannel::flag_restriction_reason) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 9 IN FIELD flags ]"); } break;
 	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
 	}
 }
@@ -1526,20 +1402,6 @@ void _serialize_messageMediaPhoto(MTPStringLogger &to, int32 stage, int32 lev, T
 	}
 }
 
-void _serialize_messageMediaVideo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ messageMediaVideo");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  video: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  caption: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_messageMediaGeo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -1587,19 +1449,6 @@ void _serialize_messageMediaDocument(MTPStringLogger &to, int32 stage, int32 lev
 	}
 }
 
-void _serialize_messageMediaAudio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ messageMediaAudio");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  audio: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_messageMediaWebPage(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -1870,41 +1719,6 @@ void _serialize_photoCachedSize(MTPStringLogger &to, int32 stage, int32 lev, Typ
 	}
 }
 
-void _serialize_videoEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ videoEmpty");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_video(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ video");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  size: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 6: to.add("  thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 7: to.add("  dc_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 8: to.add("  w: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 9: to.add("  h: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_geoPointEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	to.add("{ geoPointEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
@@ -2189,20 +2003,6 @@ void _serialize_contactBlocked(MTPStringLogger &to, int32 stage, int32 lev, Type
 	}
 }
 
-void _serialize_contactSuggested(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ contactSuggested");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  user_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  mutual_contacts: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_contactStatus(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -2294,20 +2094,6 @@ void _serialize_contacts_blockedSlice(MTPStringLogger &to, int32 stage, int32 le
 	}
 }
 
-void _serialize_contacts_suggested(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ contacts_suggested");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  results: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  users: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_messages_dialogs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -2458,14 +2244,6 @@ void _serialize_inputMessagesFilterDocument(MTPStringLogger &to, int32 stage, in
 	to.add("{ inputMessagesFilterDocument }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
 
-void _serialize_inputMessagesFilterAudio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	to.add("{ inputMessagesFilterAudio }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
-}
-
-void _serialize_inputMessagesFilterAudioDocuments(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	to.add("{ inputMessagesFilterAudioDocuments }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
-}
-
 void _serialize_inputMessagesFilterUrl(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	to.add("{ inputMessagesFilterUrl }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
@@ -2474,6 +2252,14 @@ void _serialize_inputMessagesFilterGif(MTPStringLogger &to, int32 stage, int32 l
 	to.add("{ inputMessagesFilterGif }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
 
+void _serialize_inputMessagesFilterVoice(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	to.add("{ inputMessagesFilterVoice }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
+}
+
+void _serialize_inputMessagesFilterMusic(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	to.add("{ inputMessagesFilterMusic }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
+}
+
 void _serialize_updateNewMessage(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -3067,6 +2853,21 @@ void _serialize_updateBotInlineQuery(MTPStringLogger &to, int32 stage, int32 lev
 	}
 }
 
+void _serialize_updateBotInlineSend(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	if (stage) {
+		to.add(",\n").addSpaces(lev);
+	} else {
+		to.add("{ updateBotInlineSend");
+		to.add("\n").addSpaces(lev);
+	}
+	switch (stage) {
+	case 0: to.add("  user_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 1: to.add("  query: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 2: to.add("  id: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
+	}
+}
+
 void _serialize_updates_state(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -3673,24 +3474,6 @@ void _serialize_messages_sentEncryptedFile(MTPStringLogger &to, int32 stage, int
 	}
 }
 
-void _serialize_inputAudioEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	to.add("{ inputAudioEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
-}
-
-void _serialize_inputAudio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ inputAudio");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_inputDocumentEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	to.add("{ inputDocumentEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
@@ -3709,38 +3492,6 @@ void _serialize_inputDocument(MTPStringLogger &to, int32 stage, int32 lev, Types
 	}
 }
 
-void _serialize_audioEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ audioEmpty");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
-void _serialize_audio(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ audio");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  mime_type: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  size: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 6: to.add("  dc_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_documentEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -3908,10 +3659,18 @@ void _serialize_inputPrivacyKeyStatusTimestamp(MTPStringLogger &to, int32 stage,
 	to.add("{ inputPrivacyKeyStatusTimestamp }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
 
+void _serialize_inputPrivacyKeyChatInvite(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	to.add("{ inputPrivacyKeyChatInvite }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
+}
+
 void _serialize_privacyKeyStatusTimestamp(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	to.add("{ privacyKeyStatusTimestamp }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
 
+void _serialize_privacyKeyChatInvite(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	to.add("{ privacyKeyChatInvite }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
+}
+
 void _serialize_inputPrivacyValueAllowContacts(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	to.add("{ inputPrivacyValueAllowContacts }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
 }
@@ -4092,9 +3851,12 @@ void _serialize_documentAttributeAudio(MTPStringLogger &to, int32 stage, int32 l
 		to.add("\n").addSpaces(lev);
 	}
 	switch (stage) {
-	case 0: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 1: to.add("  title: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  performer: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 0: to.add("  flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 1: to.add("  voice: "); ++stages.back(); if (flag & MTPDdocumentAttributeAudio::flag_voice) { to.add("YES [ BY BIT 10 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 10 IN FIELD flags ]"); } break;
+	case 2: to.add("  duration: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 3: to.add("  title: "); ++stages.back(); if (flag & MTPDdocumentAttributeAudio::flag_title) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
+	case 4: to.add("  performer: "); ++stages.back(); if (flag & MTPDdocumentAttributeAudio::flag_performer) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
+	case 5: to.add("  waveform: "); ++stages.back(); if (flag & MTPDdocumentAttributeAudio::flag_waveform) { types.push_back(mtpc_bytes); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
 	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
 	}
 }
@@ -6373,19 +6135,6 @@ void _serialize_contacts_importContacts(MTPStringLogger &to, int32 stage, int32
 	}
 }
 
-void _serialize_contacts_getSuggested(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
-	if (stage) {
-		to.add(",\n").addSpaces(lev);
-	} else {
-		to.add("{ contacts_getSuggested");
-		to.add("\n").addSpaces(lev);
-	}
-	switch (stage) {
-	case 0: to.add("  limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
-	}
-}
-
 void _serialize_contacts_deleteContact(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -6482,10 +6231,11 @@ void _serialize_messages_getHistory(MTPStringLogger &to, int32 stage, int32 lev,
 	switch (stage) {
 	case 0: to.add("  peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
 	case 1: to.add("  offset_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  add_offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  max_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  min_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 2: to.add("  offset_date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 3: to.add("  add_offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 4: to.add("  limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 5: to.add("  max_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 6: to.add("  min_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
 	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
 	}
 }
@@ -6539,10 +6289,11 @@ void _serialize_channels_getImportantHistory(MTPStringLogger &to, int32 stage, i
 	switch (stage) {
 	case 0: to.add("  channel: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
 	case 1: to.add("  offset_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 2: to.add("  add_offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 3: to.add("  limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 4: to.add("  max_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
-	case 5: to.add("  min_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 2: to.add("  offset_date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 3: to.add("  add_offset: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 4: to.add("  limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 5: to.add("  max_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 6: to.add("  min_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
 	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
 	}
 }
@@ -7063,6 +6814,20 @@ void _serialize_channels_deleteChannel(MTPStringLogger &to, int32 stage, int32 l
 	}
 }
 
+void _serialize_channels_toggleInvites(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
+	if (stage) {
+		to.add(",\n").addSpaces(lev);
+	} else {
+		to.add("{ channels_toggleInvites");
+		to.add("\n").addSpaces(lev);
+	}
+	switch (stage) {
+	case 0: to.add("  channel: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	case 1: to.add("  enabled: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
+	default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
+	}
+}
+
 void _serialize_messages_getChats(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {
 	if (stage) {
 		to.add(",\n").addSpaces(lev);
@@ -7675,11 +7440,6 @@ namespace {
 		_serializers.insert(mtpc_inputMediaPhoto, _serialize_inputMediaPhoto);
 		_serializers.insert(mtpc_inputMediaGeoPoint, _serialize_inputMediaGeoPoint);
 		_serializers.insert(mtpc_inputMediaContact, _serialize_inputMediaContact);
-		_serializers.insert(mtpc_inputMediaUploadedVideo, _serialize_inputMediaUploadedVideo);
-		_serializers.insert(mtpc_inputMediaUploadedThumbVideo, _serialize_inputMediaUploadedThumbVideo);
-		_serializers.insert(mtpc_inputMediaVideo, _serialize_inputMediaVideo);
-		_serializers.insert(mtpc_inputMediaUploadedAudio, _serialize_inputMediaUploadedAudio);
-		_serializers.insert(mtpc_inputMediaAudio, _serialize_inputMediaAudio);
 		_serializers.insert(mtpc_inputMediaUploadedDocument, _serialize_inputMediaUploadedDocument);
 		_serializers.insert(mtpc_inputMediaUploadedThumbDocument, _serialize_inputMediaUploadedThumbDocument);
 		_serializers.insert(mtpc_inputMediaDocument, _serialize_inputMediaDocument);
@@ -7692,12 +7452,8 @@ namespace {
 		_serializers.insert(mtpc_inputGeoPoint, _serialize_inputGeoPoint);
 		_serializers.insert(mtpc_inputPhotoEmpty, _serialize_inputPhotoEmpty);
 		_serializers.insert(mtpc_inputPhoto, _serialize_inputPhoto);
-		_serializers.insert(mtpc_inputVideoEmpty, _serialize_inputVideoEmpty);
-		_serializers.insert(mtpc_inputVideo, _serialize_inputVideo);
 		_serializers.insert(mtpc_inputFileLocation, _serialize_inputFileLocation);
-		_serializers.insert(mtpc_inputVideoFileLocation, _serialize_inputVideoFileLocation);
 		_serializers.insert(mtpc_inputEncryptedFileLocation, _serialize_inputEncryptedFileLocation);
-		_serializers.insert(mtpc_inputAudioFileLocation, _serialize_inputAudioFileLocation);
 		_serializers.insert(mtpc_inputDocumentFileLocation, _serialize_inputDocumentFileLocation);
 		_serializers.insert(mtpc_inputPhotoCropAuto, _serialize_inputPhotoCropAuto);
 		_serializers.insert(mtpc_inputPhotoCrop, _serialize_inputPhotoCrop);
@@ -7746,12 +7502,10 @@ namespace {
 		_serializers.insert(mtpc_messageService, _serialize_messageService);
 		_serializers.insert(mtpc_messageMediaEmpty, _serialize_messageMediaEmpty);
 		_serializers.insert(mtpc_messageMediaPhoto, _serialize_messageMediaPhoto);
-		_serializers.insert(mtpc_messageMediaVideo, _serialize_messageMediaVideo);
 		_serializers.insert(mtpc_messageMediaGeo, _serialize_messageMediaGeo);
 		_serializers.insert(mtpc_messageMediaContact, _serialize_messageMediaContact);
 		_serializers.insert(mtpc_messageMediaUnsupported, _serialize_messageMediaUnsupported);
 		_serializers.insert(mtpc_messageMediaDocument, _serialize_messageMediaDocument);
-		_serializers.insert(mtpc_messageMediaAudio, _serialize_messageMediaAudio);
 		_serializers.insert(mtpc_messageMediaWebPage, _serialize_messageMediaWebPage);
 		_serializers.insert(mtpc_messageMediaVenue, _serialize_messageMediaVenue);
 		_serializers.insert(mtpc_messageActionEmpty, _serialize_messageActionEmpty);
@@ -7772,8 +7526,6 @@ namespace {
 		_serializers.insert(mtpc_photoSizeEmpty, _serialize_photoSizeEmpty);
 		_serializers.insert(mtpc_photoSize, _serialize_photoSize);
 		_serializers.insert(mtpc_photoCachedSize, _serialize_photoCachedSize);
-		_serializers.insert(mtpc_videoEmpty, _serialize_videoEmpty);
-		_serializers.insert(mtpc_video, _serialize_video);
 		_serializers.insert(mtpc_geoPointEmpty, _serialize_geoPointEmpty);
 		_serializers.insert(mtpc_geoPoint, _serialize_geoPoint);
 		_serializers.insert(mtpc_auth_checkedPhone, _serialize_auth_checkedPhone);
@@ -7802,7 +7554,6 @@ namespace {
 		_serializers.insert(mtpc_contact, _serialize_contact);
 		_serializers.insert(mtpc_importedContact, _serialize_importedContact);
 		_serializers.insert(mtpc_contactBlocked, _serialize_contactBlocked);
-		_serializers.insert(mtpc_contactSuggested, _serialize_contactSuggested);
 		_serializers.insert(mtpc_contactStatus, _serialize_contactStatus);
 		_serializers.insert(mtpc_contacts_link, _serialize_contacts_link);
 		_serializers.insert(mtpc_contacts_contactsNotModified, _serialize_contacts_contactsNotModified);
@@ -7810,7 +7561,6 @@ namespace {
 		_serializers.insert(mtpc_contacts_importedContacts, _serialize_contacts_importedContacts);
 		_serializers.insert(mtpc_contacts_blocked, _serialize_contacts_blocked);
 		_serializers.insert(mtpc_contacts_blockedSlice, _serialize_contacts_blockedSlice);
-		_serializers.insert(mtpc_contacts_suggested, _serialize_contacts_suggested);
 		_serializers.insert(mtpc_messages_dialogs, _serialize_messages_dialogs);
 		_serializers.insert(mtpc_messages_dialogsSlice, _serialize_messages_dialogsSlice);
 		_serializers.insert(mtpc_messages_messages, _serialize_messages_messages);
@@ -7825,10 +7575,10 @@ namespace {
 		_serializers.insert(mtpc_inputMessagesFilterPhotoVideo, _serialize_inputMessagesFilterPhotoVideo);
 		_serializers.insert(mtpc_inputMessagesFilterPhotoVideoDocuments, _serialize_inputMessagesFilterPhotoVideoDocuments);
 		_serializers.insert(mtpc_inputMessagesFilterDocument, _serialize_inputMessagesFilterDocument);
-		_serializers.insert(mtpc_inputMessagesFilterAudio, _serialize_inputMessagesFilterAudio);
-		_serializers.insert(mtpc_inputMessagesFilterAudioDocuments, _serialize_inputMessagesFilterAudioDocuments);
 		_serializers.insert(mtpc_inputMessagesFilterUrl, _serialize_inputMessagesFilterUrl);
 		_serializers.insert(mtpc_inputMessagesFilterGif, _serialize_inputMessagesFilterGif);
+		_serializers.insert(mtpc_inputMessagesFilterVoice, _serialize_inputMessagesFilterVoice);
+		_serializers.insert(mtpc_inputMessagesFilterMusic, _serialize_inputMessagesFilterMusic);
 		_serializers.insert(mtpc_updateNewMessage, _serialize_updateNewMessage);
 		_serializers.insert(mtpc_updateMessageID, _serialize_updateMessageID);
 		_serializers.insert(mtpc_updateDeleteMessages, _serialize_updateDeleteMessages);
@@ -7871,6 +7621,7 @@ namespace {
 		_serializers.insert(mtpc_updateStickerSets, _serialize_updateStickerSets);
 		_serializers.insert(mtpc_updateSavedGifs, _serialize_updateSavedGifs);
 		_serializers.insert(mtpc_updateBotInlineQuery, _serialize_updateBotInlineQuery);
+		_serializers.insert(mtpc_updateBotInlineSend, _serialize_updateBotInlineSend);
 		_serializers.insert(mtpc_updates_state, _serialize_updates_state);
 		_serializers.insert(mtpc_updates_differenceEmpty, _serialize_updates_differenceEmpty);
 		_serializers.insert(mtpc_updates_difference, _serialize_updates_difference);
@@ -7910,12 +7661,8 @@ namespace {
 		_serializers.insert(mtpc_messages_dhConfig, _serialize_messages_dhConfig);
 		_serializers.insert(mtpc_messages_sentEncryptedMessage, _serialize_messages_sentEncryptedMessage);
 		_serializers.insert(mtpc_messages_sentEncryptedFile, _serialize_messages_sentEncryptedFile);
-		_serializers.insert(mtpc_inputAudioEmpty, _serialize_inputAudioEmpty);
-		_serializers.insert(mtpc_inputAudio, _serialize_inputAudio);
 		_serializers.insert(mtpc_inputDocumentEmpty, _serialize_inputDocumentEmpty);
 		_serializers.insert(mtpc_inputDocument, _serialize_inputDocument);
-		_serializers.insert(mtpc_audioEmpty, _serialize_audioEmpty);
-		_serializers.insert(mtpc_audio, _serialize_audio);
 		_serializers.insert(mtpc_documentEmpty, _serialize_documentEmpty);
 		_serializers.insert(mtpc_document, _serialize_document);
 		_serializers.insert(mtpc_help_support, _serialize_help_support);
@@ -7935,7 +7682,9 @@ namespace {
 		_serializers.insert(mtpc_sendMessageChooseContactAction, _serialize_sendMessageChooseContactAction);
 		_serializers.insert(mtpc_contacts_found, _serialize_contacts_found);
 		_serializers.insert(mtpc_inputPrivacyKeyStatusTimestamp, _serialize_inputPrivacyKeyStatusTimestamp);
+		_serializers.insert(mtpc_inputPrivacyKeyChatInvite, _serialize_inputPrivacyKeyChatInvite);
 		_serializers.insert(mtpc_privacyKeyStatusTimestamp, _serialize_privacyKeyStatusTimestamp);
+		_serializers.insert(mtpc_privacyKeyChatInvite, _serialize_privacyKeyChatInvite);
 		_serializers.insert(mtpc_inputPrivacyValueAllowContacts, _serialize_inputPrivacyValueAllowContacts);
 		_serializers.insert(mtpc_inputPrivacyValueAllowAll, _serialize_inputPrivacyValueAllowAll);
 		_serializers.insert(mtpc_inputPrivacyValueAllowUsers, _serialize_inputPrivacyValueAllowUsers);
@@ -8133,7 +7882,6 @@ namespace {
 		_serializers.insert(mtpc_contacts_getStatuses, _serialize_contacts_getStatuses);
 		_serializers.insert(mtpc_contacts_getContacts, _serialize_contacts_getContacts);
 		_serializers.insert(mtpc_contacts_importContacts, _serialize_contacts_importContacts);
-		_serializers.insert(mtpc_contacts_getSuggested, _serialize_contacts_getSuggested);
 		_serializers.insert(mtpc_contacts_deleteContact, _serialize_contacts_deleteContact);
 		_serializers.insert(mtpc_contacts_getBlocked, _serialize_contacts_getBlocked);
 		_serializers.insert(mtpc_contacts_exportCard, _serialize_contacts_exportCard);
@@ -8180,6 +7928,7 @@ namespace {
 		_serializers.insert(mtpc_channels_inviteToChannel, _serialize_channels_inviteToChannel);
 		_serializers.insert(mtpc_channels_kickFromChannel, _serialize_channels_kickFromChannel);
 		_serializers.insert(mtpc_channels_deleteChannel, _serialize_channels_deleteChannel);
+		_serializers.insert(mtpc_channels_toggleInvites, _serialize_channels_toggleInvites);
 		_serializers.insert(mtpc_messages_getChats, _serialize_messages_getChats);
 		_serializers.insert(mtpc_channels_getChannels, _serialize_channels_getChannels);
 		_serializers.insert(mtpc_messages_getFullChat, _serialize_messages_getFullChat);
diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h
index 09450332c..1f32eb20e 100644
--- a/Telegram/SourceFiles/mtproto/mtpScheme.h
+++ b/Telegram/SourceFiles/mtproto/mtpScheme.h
@@ -87,11 +87,6 @@ enum {
 	mtpc_inputMediaPhoto = 0xe9bfb4f3,
 	mtpc_inputMediaGeoPoint = 0xf9c44144,
 	mtpc_inputMediaContact = 0xa6e45987,
-	mtpc_inputMediaUploadedVideo = 0x82713fdf,
-	mtpc_inputMediaUploadedThumbVideo = 0x7780ddf9,
-	mtpc_inputMediaVideo = 0x936a4ebd,
-	mtpc_inputMediaUploadedAudio = 0x4e498cab,
-	mtpc_inputMediaAudio = 0x89938781,
 	mtpc_inputMediaUploadedDocument = 0x1d89306d,
 	mtpc_inputMediaUploadedThumbDocument = 0xad613491,
 	mtpc_inputMediaDocument = 0x1a77f29c,
@@ -104,12 +99,8 @@ enum {
 	mtpc_inputGeoPoint = 0xf3b7acc9,
 	mtpc_inputPhotoEmpty = 0x1cd7bf0d,
 	mtpc_inputPhoto = 0xfb95c6c4,
-	mtpc_inputVideoEmpty = 0x5508ec75,
-	mtpc_inputVideo = 0xee579652,
 	mtpc_inputFileLocation = 0x14637196,
-	mtpc_inputVideoFileLocation = 0x3d0364ec,
 	mtpc_inputEncryptedFileLocation = 0xf5235d55,
-	mtpc_inputAudioFileLocation = 0x74dc404d,
 	mtpc_inputDocumentFileLocation = 0x4e45abe9,
 	mtpc_inputPhotoCropAuto = 0xade6b004,
 	mtpc_inputPhotoCrop = 0xd9915325,
@@ -158,12 +149,10 @@ enum {
 	mtpc_messageService = 0xc06b9607,
 	mtpc_messageMediaEmpty = 0x3ded6320,
 	mtpc_messageMediaPhoto = 0x3d8ce53d,
-	mtpc_messageMediaVideo = 0x5bcf1675,
 	mtpc_messageMediaGeo = 0x56e0d474,
 	mtpc_messageMediaContact = 0x5e7d2f39,
 	mtpc_messageMediaUnsupported = 0x9f84f49e,
 	mtpc_messageMediaDocument = 0xf3e02ea8,
-	mtpc_messageMediaAudio = 0xc6b68300,
 	mtpc_messageMediaWebPage = 0xa32dd600,
 	mtpc_messageMediaVenue = 0x7912b71f,
 	mtpc_messageActionEmpty = 0xb6aef7b0,
@@ -184,8 +173,6 @@ enum {
 	mtpc_photoSizeEmpty = 0xe17e23c,
 	mtpc_photoSize = 0x77bfb61b,
 	mtpc_photoCachedSize = 0xe9a734fa,
-	mtpc_videoEmpty = 0xc10658a8,
-	mtpc_video = 0xf72887d3,
 	mtpc_geoPointEmpty = 0x1117dd5f,
 	mtpc_geoPoint = 0x2049d70c,
 	mtpc_auth_checkedPhone = 0x811ea28e,
@@ -214,7 +201,6 @@ enum {
 	mtpc_contact = 0xf911c994,
 	mtpc_importedContact = 0xd0028438,
 	mtpc_contactBlocked = 0x561bc879,
-	mtpc_contactSuggested = 0x3de191a1,
 	mtpc_contactStatus = 0xd3680c61,
 	mtpc_contacts_link = 0x3ace484c,
 	mtpc_contacts_contactsNotModified = 0xb74ba9d2,
@@ -222,7 +208,6 @@ enum {
 	mtpc_contacts_importedContacts = 0xad524315,
 	mtpc_contacts_blocked = 0x1c138d15,
 	mtpc_contacts_blockedSlice = 0x900802a1,
-	mtpc_contacts_suggested = 0x5649dcc5,
 	mtpc_messages_dialogs = 0x15ba6c40,
 	mtpc_messages_dialogsSlice = 0x71e094f3,
 	mtpc_messages_messages = 0x8c718e87,
@@ -237,10 +222,10 @@ enum {
 	mtpc_inputMessagesFilterPhotoVideo = 0x56e9f0e4,
 	mtpc_inputMessagesFilterPhotoVideoDocuments = 0xd95e73bb,
 	mtpc_inputMessagesFilterDocument = 0x9eddf188,
-	mtpc_inputMessagesFilterAudio = 0xcfc87522,
-	mtpc_inputMessagesFilterAudioDocuments = 0x5afbf764,
 	mtpc_inputMessagesFilterUrl = 0x7ef0dd87,
 	mtpc_inputMessagesFilterGif = 0xffc86587,
+	mtpc_inputMessagesFilterVoice = 0x50f5c392,
+	mtpc_inputMessagesFilterMusic = 0x3751b49e,
 	mtpc_updateNewMessage = 0x1f2b0afd,
 	mtpc_updateMessageID = 0x4e90bfd6,
 	mtpc_updateDeleteMessages = 0xa20db0e5,
@@ -283,6 +268,7 @@ enum {
 	mtpc_updateStickerSets = 0x43ae3dec,
 	mtpc_updateSavedGifs = 0x9375341e,
 	mtpc_updateBotInlineQuery = 0xc01eea08,
+	mtpc_updateBotInlineSend = 0xf69e113,
 	mtpc_updates_state = 0xa56c2a3e,
 	mtpc_updates_differenceEmpty = 0x5d75a138,
 	mtpc_updates_difference = 0xf49ca0,
@@ -322,12 +308,8 @@ enum {
 	mtpc_messages_dhConfig = 0x2c221edd,
 	mtpc_messages_sentEncryptedMessage = 0x560f8935,
 	mtpc_messages_sentEncryptedFile = 0x9493ff32,
-	mtpc_inputAudioEmpty = 0xd95adc84,
-	mtpc_inputAudio = 0x77d440ff,
 	mtpc_inputDocumentEmpty = 0x72f0eaae,
 	mtpc_inputDocument = 0x18798952,
-	mtpc_audioEmpty = 0x586988d8,
-	mtpc_audio = 0xf9e35055,
 	mtpc_documentEmpty = 0x36f8c871,
 	mtpc_document = 0xf9a39f4f,
 	mtpc_help_support = 0x17c6b5f6,
@@ -347,7 +329,9 @@ enum {
 	mtpc_sendMessageChooseContactAction = 0x628cbc6f,
 	mtpc_contacts_found = 0x1aa1f784,
 	mtpc_inputPrivacyKeyStatusTimestamp = 0x4f96cb18,
+	mtpc_inputPrivacyKeyChatInvite = 0xbdfb0426,
 	mtpc_privacyKeyStatusTimestamp = 0xbc2eab30,
+	mtpc_privacyKeyChatInvite = 0x500e6dfa,
 	mtpc_inputPrivacyValueAllowContacts = 0xd09e07b,
 	mtpc_inputPrivacyValueAllowAll = 0x184b35ce,
 	mtpc_inputPrivacyValueAllowUsers = 0x131cc67f,
@@ -367,7 +351,7 @@ enum {
 	mtpc_documentAttributeAnimated = 0x11b58939,
 	mtpc_documentAttributeSticker = 0x3a556302,
 	mtpc_documentAttributeVideo = 0x5910cccb,
-	mtpc_documentAttributeAudio = 0xded218e0,
+	mtpc_documentAttributeAudio = 0x9852f9c6,
 	mtpc_documentAttributeFilename = 0x15590068,
 	mtpc_messages_stickersNotModified = 0xf1749a22,
 	mtpc_messages_stickers = 0x8a8ecd32,
@@ -388,7 +372,7 @@ enum {
 	mtpc_account_noPassword = 0x96dabc18,
 	mtpc_account_password = 0x7c18141c,
 	mtpc_account_passwordSettings = 0xb7b72ab3,
-	mtpc_account_passwordInputSettings = 0xbcfc532c,
+	mtpc_account_passwordInputSettings = 0x86916deb,
 	mtpc_auth_passwordRecovery = 0x137948a5,
 	mtpc_receivedNotifyMessage = 0xa384b779,
 	mtpc_chatInviteEmpty = 0x69df3769,
@@ -512,7 +496,6 @@ enum {
 	mtpc_contacts_getStatuses = 0xc4a353ee,
 	mtpc_contacts_getContacts = 0x22c6aa08,
 	mtpc_contacts_importContacts = 0xda30b32d,
-	mtpc_contacts_getSuggested = 0xcd773428,
 	mtpc_contacts_deleteContact = 0x8e953744,
 	mtpc_contacts_deleteContacts = 0x59ab389e,
 	mtpc_contacts_block = 0x332b49fc,
@@ -524,7 +507,7 @@ enum {
 	mtpc_contacts_resolveUsername = 0xf93ccba3,
 	mtpc_messages_getMessages = 0x4222fa74,
 	mtpc_messages_getDialogs = 0x6b47f94d,
-	mtpc_messages_getHistory = 0x8a8ec2da,
+	mtpc_messages_getHistory = 0xafa92846,
 	mtpc_messages_search = 0xd4569248,
 	mtpc_messages_readHistory = 0xe306d3a,
 	mtpc_messages_deleteHistory = 0xb7c13bd9,
@@ -597,7 +580,7 @@ enum {
 	mtpc_help_getAppChangelog = 0x5bab7fb2,
 	mtpc_help_getTermsOfService = 0x37d78f83,
 	mtpc_channels_getDialogs = 0xa9d3d249,
-	mtpc_channels_getImportantHistory = 0xddb929cb,
+	mtpc_channels_getImportantHistory = 0x8f494bb2,
 	mtpc_channels_readHistory = 0xcc104937,
 	mtpc_channels_deleteMessages = 0x84c1fd4e,
 	mtpc_channels_deleteUserHistory = 0xd10dd71b,
@@ -620,7 +603,8 @@ enum {
 	mtpc_channels_inviteToChannel = 0x199f3a6c,
 	mtpc_channels_kickFromChannel = 0xa672de14,
 	mtpc_channels_exportInvite = 0xc7560885,
-	mtpc_channels_deleteChannel = 0xc0111fe3
+	mtpc_channels_deleteChannel = 0xc0111fe3,
+	mtpc_channels_toggleInvites = 0x49609307
 };
 
 // Type forward declarations
@@ -722,11 +706,6 @@ class MTPDinputMediaUploadedPhoto;
 class MTPDinputMediaPhoto;
 class MTPDinputMediaGeoPoint;
 class MTPDinputMediaContact;
-class MTPDinputMediaUploadedVideo;
-class MTPDinputMediaUploadedThumbVideo;
-class MTPDinputMediaVideo;
-class MTPDinputMediaUploadedAudio;
-class MTPDinputMediaAudio;
 class MTPDinputMediaUploadedDocument;
 class MTPDinputMediaUploadedThumbDocument;
 class MTPDinputMediaDocument;
@@ -743,14 +722,9 @@ class MTPDinputGeoPoint;
 class MTPinputPhoto;
 class MTPDinputPhoto;
 
-class MTPinputVideo;
-class MTPDinputVideo;
-
 class MTPinputFileLocation;
 class MTPDinputFileLocation;
-class MTPDinputVideoFileLocation;
 class MTPDinputEncryptedFileLocation;
-class MTPDinputAudioFileLocation;
 class MTPDinputDocumentFileLocation;
 
 class MTPinputPhotoCrop;
@@ -811,11 +785,9 @@ class MTPDmessageService;
 
 class MTPmessageMedia;
 class MTPDmessageMediaPhoto;
-class MTPDmessageMediaVideo;
 class MTPDmessageMediaGeo;
 class MTPDmessageMediaContact;
 class MTPDmessageMediaDocument;
-class MTPDmessageMediaAudio;
 class MTPDmessageMediaWebPage;
 class MTPDmessageMediaVenue;
 
@@ -843,10 +815,6 @@ class MTPDphotoSizeEmpty;
 class MTPDphotoSize;
 class MTPDphotoCachedSize;
 
-class MTPvideo;
-class MTPDvideoEmpty;
-class MTPDvideo;
-
 class MTPgeoPoint;
 class MTPDgeoPoint;
 
@@ -895,9 +863,6 @@ class MTPDimportedContact;
 class MTPcontactBlocked;
 class MTPDcontactBlocked;
 
-class MTPcontactSuggested;
-class MTPDcontactSuggested;
-
 class MTPcontactStatus;
 class MTPDcontactStatus;
 
@@ -914,9 +879,6 @@ class MTPcontacts_blocked;
 class MTPDcontacts_blocked;
 class MTPDcontacts_blockedSlice;
 
-class MTPcontacts_suggested;
-class MTPDcontacts_suggested;
-
 class MTPmessages_dialogs;
 class MTPDmessages_dialogs;
 class MTPDmessages_dialogsSlice;
@@ -978,6 +940,7 @@ class MTPDupdateChatParticipantAdmin;
 class MTPDupdateNewStickerSet;
 class MTPDupdateStickerSetsOrder;
 class MTPDupdateBotInlineQuery;
+class MTPDupdateBotInlineSend;
 
 class MTPupdates_state;
 class MTPDupdates_state;
@@ -1050,16 +1013,9 @@ class MTPmessages_sentEncryptedMessage;
 class MTPDmessages_sentEncryptedMessage;
 class MTPDmessages_sentEncryptedFile;
 
-class MTPinputAudio;
-class MTPDinputAudio;
-
 class MTPinputDocument;
 class MTPDinputDocument;
 
-class MTPaudio;
-class MTPDaudioEmpty;
-class MTPDaudio;
-
 class MTPdocument;
 class MTPDdocumentEmpty;
 class MTPDdocument;
@@ -1306,7 +1262,6 @@ typedef MTPBoxed<MTPinputMedia> MTPInputMedia;
 typedef MTPBoxed<MTPinputChatPhoto> MTPInputChatPhoto;
 typedef MTPBoxed<MTPinputGeoPoint> MTPInputGeoPoint;
 typedef MTPBoxed<MTPinputPhoto> MTPInputPhoto;
-typedef MTPBoxed<MTPinputVideo> MTPInputVideo;
 typedef MTPBoxed<MTPinputFileLocation> MTPInputFileLocation;
 typedef MTPBoxed<MTPinputPhotoCrop> MTPInputPhotoCrop;
 typedef MTPBoxed<MTPinputAppEvent> MTPInputAppEvent;
@@ -1327,7 +1282,6 @@ typedef MTPBoxed<MTPmessageAction> MTPMessageAction;
 typedef MTPBoxed<MTPdialog> MTPDialog;
 typedef MTPBoxed<MTPphoto> MTPPhoto;
 typedef MTPBoxed<MTPphotoSize> MTPPhotoSize;
-typedef MTPBoxed<MTPvideo> MTPVideo;
 typedef MTPBoxed<MTPgeoPoint> MTPGeoPoint;
 typedef MTPBoxed<MTPauth_checkedPhone> MTPauth_CheckedPhone;
 typedef MTPBoxed<MTPauth_sentCode> MTPauth_SentCode;
@@ -1344,13 +1298,11 @@ typedef MTPBoxed<MTPuserFull> MTPUserFull;
 typedef MTPBoxed<MTPcontact> MTPContact;
 typedef MTPBoxed<MTPimportedContact> MTPImportedContact;
 typedef MTPBoxed<MTPcontactBlocked> MTPContactBlocked;
-typedef MTPBoxed<MTPcontactSuggested> MTPContactSuggested;
 typedef MTPBoxed<MTPcontactStatus> MTPContactStatus;
 typedef MTPBoxed<MTPcontacts_link> MTPcontacts_Link;
 typedef MTPBoxed<MTPcontacts_contacts> MTPcontacts_Contacts;
 typedef MTPBoxed<MTPcontacts_importedContacts> MTPcontacts_ImportedContacts;
 typedef MTPBoxed<MTPcontacts_blocked> MTPcontacts_Blocked;
-typedef MTPBoxed<MTPcontacts_suggested> MTPcontacts_Suggested;
 typedef MTPBoxed<MTPmessages_dialogs> MTPmessages_Dialogs;
 typedef MTPBoxed<MTPmessages_messages> MTPmessages_Messages;
 typedef MTPBoxed<MTPmessages_chats> MTPmessages_Chats;
@@ -1376,9 +1328,7 @@ typedef MTPBoxed<MTPinputEncryptedFile> MTPInputEncryptedFile;
 typedef MTPBoxed<MTPencryptedMessage> MTPEncryptedMessage;
 typedef MTPBoxed<MTPmessages_dhConfig> MTPmessages_DhConfig;
 typedef MTPBoxed<MTPmessages_sentEncryptedMessage> MTPmessages_SentEncryptedMessage;
-typedef MTPBoxed<MTPinputAudio> MTPInputAudio;
 typedef MTPBoxed<MTPinputDocument> MTPInputDocument;
-typedef MTPBoxed<MTPaudio> MTPAudio;
 typedef MTPBoxed<MTPdocument> MTPDocument;
 typedef MTPBoxed<MTPhelp_support> MTPhelp_Support;
 typedef MTPBoxed<MTPnotifyPeer> MTPNotifyPeer;
@@ -2556,66 +2506,6 @@ public:
 		return *(const MTPDinputMediaContact*)data;
 	}
 
-	MTPDinputMediaUploadedVideo &_inputMediaUploadedVideo() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedVideo);
-		split();
-		return *(MTPDinputMediaUploadedVideo*)data;
-	}
-	const MTPDinputMediaUploadedVideo &c_inputMediaUploadedVideo() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedVideo);
-		return *(const MTPDinputMediaUploadedVideo*)data;
-	}
-
-	MTPDinputMediaUploadedThumbVideo &_inputMediaUploadedThumbVideo() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedThumbVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedThumbVideo);
-		split();
-		return *(MTPDinputMediaUploadedThumbVideo*)data;
-	}
-	const MTPDinputMediaUploadedThumbVideo &c_inputMediaUploadedThumbVideo() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedThumbVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedThumbVideo);
-		return *(const MTPDinputMediaUploadedThumbVideo*)data;
-	}
-
-	MTPDinputMediaVideo &_inputMediaVideo() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaVideo);
-		split();
-		return *(MTPDinputMediaVideo*)data;
-	}
-	const MTPDinputMediaVideo &c_inputMediaVideo() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaVideo);
-		return *(const MTPDinputMediaVideo*)data;
-	}
-
-	MTPDinputMediaUploadedAudio &_inputMediaUploadedAudio() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedAudio);
-		split();
-		return *(MTPDinputMediaUploadedAudio*)data;
-	}
-	const MTPDinputMediaUploadedAudio &c_inputMediaUploadedAudio() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaUploadedAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedAudio);
-		return *(const MTPDinputMediaUploadedAudio*)data;
-	}
-
-	MTPDinputMediaAudio &_inputMediaAudio() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaAudio);
-		split();
-		return *(MTPDinputMediaAudio*)data;
-	}
-	const MTPDinputMediaAudio &c_inputMediaAudio() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputMediaAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaAudio);
-		return *(const MTPDinputMediaAudio*)data;
-	}
-
 	MTPDinputMediaUploadedDocument &_inputMediaUploadedDocument() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_inputMediaUploadedDocument) throw mtpErrorWrongTypeId(_type, mtpc_inputMediaUploadedDocument);
@@ -2689,11 +2579,6 @@ private:
 	explicit MTPinputMedia(MTPDinputMediaPhoto *_data);
 	explicit MTPinputMedia(MTPDinputMediaGeoPoint *_data);
 	explicit MTPinputMedia(MTPDinputMediaContact *_data);
-	explicit MTPinputMedia(MTPDinputMediaUploadedVideo *_data);
-	explicit MTPinputMedia(MTPDinputMediaUploadedThumbVideo *_data);
-	explicit MTPinputMedia(MTPDinputMediaVideo *_data);
-	explicit MTPinputMedia(MTPDinputMediaUploadedAudio *_data);
-	explicit MTPinputMedia(MTPDinputMediaAudio *_data);
 	explicit MTPinputMedia(MTPDinputMediaUploadedDocument *_data);
 	explicit MTPinputMedia(MTPDinputMediaUploadedThumbDocument *_data);
 	explicit MTPinputMedia(MTPDinputMediaDocument *_data);
@@ -2705,11 +2590,6 @@ private:
 	friend MTPinputMedia MTP_inputMediaPhoto(const MTPInputPhoto &_id, const MTPstring &_caption);
 	friend MTPinputMedia MTP_inputMediaGeoPoint(const MTPInputGeoPoint &_geo_point);
 	friend MTPinputMedia MTP_inputMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name);
-	friend MTPinputMedia MTP_inputMediaUploadedVideo(const MTPInputFile &_file, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption);
-	friend MTPinputMedia MTP_inputMediaUploadedThumbVideo(const MTPInputFile &_file, const MTPInputFile &_thumb, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption);
-	friend MTPinputMedia MTP_inputMediaVideo(const MTPInputVideo &_id, const MTPstring &_caption);
-	friend MTPinputMedia MTP_inputMediaUploadedAudio(const MTPInputFile &_file, MTPint _duration, const MTPstring &_mime_type);
-	friend MTPinputMedia MTP_inputMediaAudio(const MTPInputAudio &_id);
 	friend MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector<MTPDocumentAttribute> &_attributes, const MTPstring &_caption);
 	friend MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector<MTPDocumentAttribute> &_attributes, const MTPstring &_caption);
 	friend MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption);
@@ -2848,44 +2728,6 @@ private:
 };
 typedef MTPBoxed<MTPinputPhoto> MTPInputPhoto;
 
-class MTPinputVideo : private mtpDataOwner {
-public:
-	MTPinputVideo() : mtpDataOwner(0), _type(0) {
-	}
-	MTPinputVideo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) {
-		read(from, end, cons);
-	}
-
-	MTPDinputVideo &_inputVideo() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputVideo);
-		split();
-		return *(MTPDinputVideo*)data;
-	}
-	const MTPDinputVideo &c_inputVideo() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputVideo) throw mtpErrorWrongTypeId(_type, mtpc_inputVideo);
-		return *(const MTPDinputVideo*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPinputVideo(mtpTypeId type);
-	explicit MTPinputVideo(MTPDinputVideo *_data);
-
-	friend MTPinputVideo MTP_inputVideoEmpty();
-	friend MTPinputVideo MTP_inputVideo(const MTPlong &_id, const MTPlong &_access_hash);
-
-	mtpTypeId _type;
-};
-typedef MTPBoxed<MTPinputVideo> MTPInputVideo;
-
 class MTPinputFileLocation : private mtpDataOwner {
 public:
 	MTPinputFileLocation() : mtpDataOwner(0), _type(0) {
@@ -2906,18 +2748,6 @@ public:
 		return *(const MTPDinputFileLocation*)data;
 	}
 
-	MTPDinputVideoFileLocation &_inputVideoFileLocation() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputVideoFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputVideoFileLocation);
-		split();
-		return *(MTPDinputVideoFileLocation*)data;
-	}
-	const MTPDinputVideoFileLocation &c_inputVideoFileLocation() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputVideoFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputVideoFileLocation);
-		return *(const MTPDinputVideoFileLocation*)data;
-	}
-
 	MTPDinputEncryptedFileLocation &_inputEncryptedFileLocation() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_inputEncryptedFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputEncryptedFileLocation);
@@ -2930,18 +2760,6 @@ public:
 		return *(const MTPDinputEncryptedFileLocation*)data;
 	}
 
-	MTPDinputAudioFileLocation &_inputAudioFileLocation() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputAudioFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputAudioFileLocation);
-		split();
-		return *(MTPDinputAudioFileLocation*)data;
-	}
-	const MTPDinputAudioFileLocation &c_inputAudioFileLocation() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputAudioFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputAudioFileLocation);
-		return *(const MTPDinputAudioFileLocation*)data;
-	}
-
 	MTPDinputDocumentFileLocation &_inputDocumentFileLocation() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_inputDocumentFileLocation) throw mtpErrorWrongTypeId(_type, mtpc_inputDocumentFileLocation);
@@ -2964,15 +2782,11 @@ public:
 private:
 	explicit MTPinputFileLocation(mtpTypeId type);
 	explicit MTPinputFileLocation(MTPDinputFileLocation *_data);
-	explicit MTPinputFileLocation(MTPDinputVideoFileLocation *_data);
 	explicit MTPinputFileLocation(MTPDinputEncryptedFileLocation *_data);
-	explicit MTPinputFileLocation(MTPDinputAudioFileLocation *_data);
 	explicit MTPinputFileLocation(MTPDinputDocumentFileLocation *_data);
 
 	friend MTPinputFileLocation MTP_inputFileLocation(const MTPlong &_volume_id, MTPint _local_id, const MTPlong &_secret);
-	friend MTPinputFileLocation MTP_inputVideoFileLocation(const MTPlong &_id, const MTPlong &_access_hash);
 	friend MTPinputFileLocation MTP_inputEncryptedFileLocation(const MTPlong &_id, const MTPlong &_access_hash);
-	friend MTPinputFileLocation MTP_inputAudioFileLocation(const MTPlong &_id, const MTPlong &_access_hash);
 	friend MTPinputFileLocation MTP_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash);
 
 	mtpTypeId _type;
@@ -3724,18 +3538,6 @@ public:
 		return *(const MTPDmessageMediaPhoto*)data;
 	}
 
-	MTPDmessageMediaVideo &_messageMediaVideo() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_messageMediaVideo) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaVideo);
-		split();
-		return *(MTPDmessageMediaVideo*)data;
-	}
-	const MTPDmessageMediaVideo &c_messageMediaVideo() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_messageMediaVideo) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaVideo);
-		return *(const MTPDmessageMediaVideo*)data;
-	}
-
 	MTPDmessageMediaGeo &_messageMediaGeo() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_messageMediaGeo) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaGeo);
@@ -3772,18 +3574,6 @@ public:
 		return *(const MTPDmessageMediaDocument*)data;
 	}
 
-	MTPDmessageMediaAudio &_messageMediaAudio() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_messageMediaAudio) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaAudio);
-		split();
-		return *(MTPDmessageMediaAudio*)data;
-	}
-	const MTPDmessageMediaAudio &c_messageMediaAudio() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_messageMediaAudio) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaAudio);
-		return *(const MTPDmessageMediaAudio*)data;
-	}
-
 	MTPDmessageMediaWebPage &_messageMediaWebPage() {
 		if (!data) throw mtpErrorUninitialized();
 		if (_type != mtpc_messageMediaWebPage) throw mtpErrorWrongTypeId(_type, mtpc_messageMediaWebPage);
@@ -3818,22 +3608,18 @@ public:
 private:
 	explicit MTPmessageMedia(mtpTypeId type);
 	explicit MTPmessageMedia(MTPDmessageMediaPhoto *_data);
-	explicit MTPmessageMedia(MTPDmessageMediaVideo *_data);
 	explicit MTPmessageMedia(MTPDmessageMediaGeo *_data);
 	explicit MTPmessageMedia(MTPDmessageMediaContact *_data);
 	explicit MTPmessageMedia(MTPDmessageMediaDocument *_data);
-	explicit MTPmessageMedia(MTPDmessageMediaAudio *_data);
 	explicit MTPmessageMedia(MTPDmessageMediaWebPage *_data);
 	explicit MTPmessageMedia(MTPDmessageMediaVenue *_data);
 
 	friend MTPmessageMedia MTP_messageMediaEmpty();
 	friend MTPmessageMedia MTP_messageMediaPhoto(const MTPPhoto &_photo, const MTPstring &_caption);
-	friend MTPmessageMedia MTP_messageMediaVideo(const MTPVideo &_video, const MTPstring &_caption);
 	friend MTPmessageMedia MTP_messageMediaGeo(const MTPGeoPoint &_geo);
 	friend MTPmessageMedia MTP_messageMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name, MTPint _user_id);
 	friend MTPmessageMedia MTP_messageMediaUnsupported();
 	friend MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document, const MTPstring &_caption);
-	friend MTPmessageMedia MTP_messageMediaAudio(const MTPAudio &_audio);
 	friend MTPmessageMedia MTP_messageMediaWebPage(const MTPWebPage &_webpage);
 	friend MTPmessageMedia MTP_messageMediaVenue(const MTPGeoPoint &_geo, const MTPstring &_title, const MTPstring &_address, const MTPstring &_provider, const MTPstring &_venue_id);
 
@@ -4159,57 +3945,6 @@ private:
 };
 typedef MTPBoxed<MTPphotoSize> MTPPhotoSize;
 
-class MTPvideo : private mtpDataOwner {
-public:
-	MTPvideo() : mtpDataOwner(0), _type(0) {
-	}
-	MTPvideo(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) {
-		read(from, end, cons);
-	}
-
-	MTPDvideoEmpty &_videoEmpty() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_videoEmpty) throw mtpErrorWrongTypeId(_type, mtpc_videoEmpty);
-		split();
-		return *(MTPDvideoEmpty*)data;
-	}
-	const MTPDvideoEmpty &c_videoEmpty() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_videoEmpty) throw mtpErrorWrongTypeId(_type, mtpc_videoEmpty);
-		return *(const MTPDvideoEmpty*)data;
-	}
-
-	MTPDvideo &_video() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_video) throw mtpErrorWrongTypeId(_type, mtpc_video);
-		split();
-		return *(MTPDvideo*)data;
-	}
-	const MTPDvideo &c_video() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_video) throw mtpErrorWrongTypeId(_type, mtpc_video);
-		return *(const MTPDvideo*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPvideo(mtpTypeId type);
-	explicit MTPvideo(MTPDvideoEmpty *_data);
-	explicit MTPvideo(MTPDvideo *_data);
-
-	friend MTPvideo MTP_videoEmpty(const MTPlong &_id);
-	friend MTPvideo MTP_video(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _w, MTPint _h);
-
-	mtpTypeId _type;
-};
-typedef MTPBoxed<MTPvideo> MTPVideo;
-
 class MTPgeoPoint : private mtpDataOwner {
 public:
 	MTPgeoPoint() : mtpDataOwner(0), _type(0) {
@@ -4766,37 +4501,6 @@ private:
 };
 typedef MTPBoxed<MTPcontactBlocked> MTPContactBlocked;
 
-class MTPcontactSuggested : private mtpDataOwner {
-public:
-	MTPcontactSuggested();
-	MTPcontactSuggested(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contactSuggested) : mtpDataOwner(0) {
-		read(from, end, cons);
-	}
-
-	MTPDcontactSuggested &_contactSuggested() {
-		if (!data) throw mtpErrorUninitialized();
-		split();
-		return *(MTPDcontactSuggested*)data;
-	}
-	const MTPDcontactSuggested &c_contactSuggested() const {
-		if (!data) throw mtpErrorUninitialized();
-		return *(const MTPDcontactSuggested*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contactSuggested);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPcontactSuggested(MTPDcontactSuggested *_data);
-
-	friend MTPcontactSuggested MTP_contactSuggested(MTPint _user_id, MTPint _mutual_contacts);
-};
-typedef MTPBoxed<MTPcontactSuggested> MTPContactSuggested;
-
 class MTPcontactStatus : private mtpDataOwner {
 public:
 	MTPcontactStatus();
@@ -4979,37 +4683,6 @@ private:
 };
 typedef MTPBoxed<MTPcontacts_blocked> MTPcontacts_Blocked;
 
-class MTPcontacts_suggested : private mtpDataOwner {
-public:
-	MTPcontacts_suggested();
-	MTPcontacts_suggested(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_suggested) : mtpDataOwner(0) {
-		read(from, end, cons);
-	}
-
-	MTPDcontacts_suggested &_contacts_suggested() {
-		if (!data) throw mtpErrorUninitialized();
-		split();
-		return *(MTPDcontacts_suggested*)data;
-	}
-	const MTPDcontacts_suggested &c_contacts_suggested() const {
-		if (!data) throw mtpErrorUninitialized();
-		return *(const MTPDcontacts_suggested*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_suggested);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPcontacts_suggested(MTPDcontacts_suggested *_data);
-
-	friend MTPcontacts_suggested MTP_contacts_suggested(const MTPVector<MTPContactSuggested> &_results, const MTPVector<MTPUser> &_users);
-};
-typedef MTPBoxed<MTPcontacts_suggested> MTPcontacts_Suggested;
-
 class MTPmessages_dialogs : private mtpDataOwner {
 public:
 	MTPmessages_dialogs() : mtpDataOwner(0), _type(0) {
@@ -5243,10 +4916,10 @@ private:
 	friend MTPmessagesFilter MTP_inputMessagesFilterPhotoVideo();
 	friend MTPmessagesFilter MTP_inputMessagesFilterPhotoVideoDocuments();
 	friend MTPmessagesFilter MTP_inputMessagesFilterDocument();
-	friend MTPmessagesFilter MTP_inputMessagesFilterAudio();
-	friend MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments();
 	friend MTPmessagesFilter MTP_inputMessagesFilterUrl();
 	friend MTPmessagesFilter MTP_inputMessagesFilterGif();
+	friend MTPmessagesFilter MTP_inputMessagesFilterVoice();
+	friend MTPmessagesFilter MTP_inputMessagesFilterMusic();
 
 	mtpTypeId _type;
 };
@@ -5740,6 +5413,18 @@ public:
 		return *(const MTPDupdateBotInlineQuery*)data;
 	}
 
+	MTPDupdateBotInlineSend &_updateBotInlineSend() {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_updateBotInlineSend) throw mtpErrorWrongTypeId(_type, mtpc_updateBotInlineSend);
+		split();
+		return *(MTPDupdateBotInlineSend*)data;
+	}
+	const MTPDupdateBotInlineSend &c_updateBotInlineSend() const {
+		if (!data) throw mtpErrorUninitialized();
+		if (_type != mtpc_updateBotInlineSend) throw mtpErrorWrongTypeId(_type, mtpc_updateBotInlineSend);
+		return *(const MTPDupdateBotInlineSend*)data;
+	}
+
 	uint32 innerLength() const;
 	mtpTypeId type() const;
 	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
@@ -5789,6 +5474,7 @@ private:
 	explicit MTPupdate(MTPDupdateNewStickerSet *_data);
 	explicit MTPupdate(MTPDupdateStickerSetsOrder *_data);
 	explicit MTPupdate(MTPDupdateBotInlineQuery *_data);
+	explicit MTPupdate(MTPDupdateBotInlineSend *_data);
 
 	friend MTPupdate MTP_updateNewMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count);
 	friend MTPupdate MTP_updateMessageID(MTPint _id, const MTPlong &_random_id);
@@ -5832,6 +5518,7 @@ private:
 	friend MTPupdate MTP_updateStickerSets();
 	friend MTPupdate MTP_updateSavedGifs();
 	friend MTPupdate MTP_updateBotInlineQuery(const MTPlong &_query_id, MTPint _user_id, const MTPstring &_query, const MTPstring &_offset);
+	friend MTPupdate MTP_updateBotInlineSend(MTPint _user_id, const MTPstring &_query, const MTPstring &_id);
 
 	mtpTypeId _type;
 };
@@ -6697,44 +6384,6 @@ private:
 };
 typedef MTPBoxed<MTPmessages_sentEncryptedMessage> MTPmessages_SentEncryptedMessage;
 
-class MTPinputAudio : private mtpDataOwner {
-public:
-	MTPinputAudio() : mtpDataOwner(0), _type(0) {
-	}
-	MTPinputAudio(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) {
-		read(from, end, cons);
-	}
-
-	MTPDinputAudio &_inputAudio() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputAudio);
-		split();
-		return *(MTPDinputAudio*)data;
-	}
-	const MTPDinputAudio &c_inputAudio() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_inputAudio) throw mtpErrorWrongTypeId(_type, mtpc_inputAudio);
-		return *(const MTPDinputAudio*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPinputAudio(mtpTypeId type);
-	explicit MTPinputAudio(MTPDinputAudio *_data);
-
-	friend MTPinputAudio MTP_inputAudioEmpty();
-	friend MTPinputAudio MTP_inputAudio(const MTPlong &_id, const MTPlong &_access_hash);
-
-	mtpTypeId _type;
-};
-typedef MTPBoxed<MTPinputAudio> MTPInputAudio;
-
 class MTPinputDocument : private mtpDataOwner {
 public:
 	MTPinputDocument() : mtpDataOwner(0), _type(0) {
@@ -6773,57 +6422,6 @@ private:
 };
 typedef MTPBoxed<MTPinputDocument> MTPInputDocument;
 
-class MTPaudio : private mtpDataOwner {
-public:
-	MTPaudio() : mtpDataOwner(0), _type(0) {
-	}
-	MTPaudio(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) {
-		read(from, end, cons);
-	}
-
-	MTPDaudioEmpty &_audioEmpty() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_audioEmpty) throw mtpErrorWrongTypeId(_type, mtpc_audioEmpty);
-		split();
-		return *(MTPDaudioEmpty*)data;
-	}
-	const MTPDaudioEmpty &c_audioEmpty() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_audioEmpty) throw mtpErrorWrongTypeId(_type, mtpc_audioEmpty);
-		return *(const MTPDaudioEmpty*)data;
-	}
-
-	MTPDaudio &_audio() {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_audio) throw mtpErrorWrongTypeId(_type, mtpc_audio);
-		split();
-		return *(MTPDaudio*)data;
-	}
-	const MTPDaudio &c_audio() const {
-		if (!data) throw mtpErrorUninitialized();
-		if (_type != mtpc_audio) throw mtpErrorWrongTypeId(_type, mtpc_audio);
-		return *(const MTPDaudio*)data;
-	}
-
-	uint32 innerLength() const;
-	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
-	void write(mtpBuffer &to) const;
-
-	typedef void ResponseType;
-
-private:
-	explicit MTPaudio(mtpTypeId type);
-	explicit MTPaudio(MTPDaudioEmpty *_data);
-	explicit MTPaudio(MTPDaudio *_data);
-
-	friend MTPaudio MTP_audioEmpty(const MTPlong &_id);
-	friend MTPaudio MTP_audio(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, MTPint _dc_id);
-
-	mtpTypeId _type;
-};
-typedef MTPBoxed<MTPaudio> MTPAudio;
-
 class MTPdocument : private mtpDataOwner {
 public:
 	MTPdocument() : mtpDataOwner(0), _type(0) {
@@ -7064,43 +6662,51 @@ typedef MTPBoxed<MTPcontacts_found> MTPcontacts_Found;
 
 class MTPinputPrivacyKey {
 public:
-	MTPinputPrivacyKey() {
+	MTPinputPrivacyKey() : _type(0) {
 	}
-	MTPinputPrivacyKey(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_inputPrivacyKeyStatusTimestamp) {
+	MTPinputPrivacyKey(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : _type(0) {
 		read(from, end, cons);
 	}
 
 	uint32 innerLength() const;
 	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_inputPrivacyKeyStatusTimestamp);
+	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
 	void write(mtpBuffer &to) const;
 
 	typedef void ResponseType;
 
 private:
+	explicit MTPinputPrivacyKey(mtpTypeId type);
 
 	friend MTPinputPrivacyKey MTP_inputPrivacyKeyStatusTimestamp();
+	friend MTPinputPrivacyKey MTP_inputPrivacyKeyChatInvite();
+
+	mtpTypeId _type;
 };
 typedef MTPBoxed<MTPinputPrivacyKey> MTPInputPrivacyKey;
 
 class MTPprivacyKey {
 public:
-	MTPprivacyKey() {
+	MTPprivacyKey() : _type(0) {
 	}
-	MTPprivacyKey(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_privacyKeyStatusTimestamp) {
+	MTPprivacyKey(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : _type(0) {
 		read(from, end, cons);
 	}
 
 	uint32 innerLength() const;
 	mtpTypeId type() const;
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_privacyKeyStatusTimestamp);
+	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
 	void write(mtpBuffer &to) const;
 
 	typedef void ResponseType;
 
 private:
+	explicit MTPprivacyKey(mtpTypeId type);
 
 	friend MTPprivacyKey MTP_privacyKeyStatusTimestamp();
+	friend MTPprivacyKey MTP_privacyKeyChatInvite();
+
+	mtpTypeId _type;
 };
 typedef MTPBoxed<MTPprivacyKey> MTPPrivacyKey;
 
@@ -7394,7 +7000,7 @@ private:
 	friend MTPdocumentAttribute MTP_documentAttributeAnimated();
 	friend MTPdocumentAttribute MTP_documentAttributeSticker(const MTPstring &_alt, const MTPInputStickerSet &_stickerset);
 	friend MTPdocumentAttribute MTP_documentAttributeVideo(MTPint _duration, MTPint _w, MTPint _h);
-	friend MTPdocumentAttribute MTP_documentAttributeAudio(MTPint _duration, const MTPstring &_title, const MTPstring &_performer);
+	friend MTPdocumentAttribute MTP_documentAttributeAudio(MTPint _flags, MTPint _duration, const MTPstring &_title, const MTPstring &_performer, const MTPbytes &_waveform);
 	friend MTPdocumentAttribute MTP_documentAttributeFilename(const MTPstring &_file_name);
 
 	mtpTypeId _type;
@@ -9808,70 +9414,6 @@ public:
 	MTPstring vlast_name;
 };
 
-class MTPDinputMediaUploadedVideo : public mtpDataImpl<MTPDinputMediaUploadedVideo> {
-public:
-	MTPDinputMediaUploadedVideo() {
-	}
-	MTPDinputMediaUploadedVideo(const MTPInputFile &_file, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption) : vfile(_file), vduration(_duration), vw(_w), vh(_h), vmime_type(_mime_type), vcaption(_caption) {
-	}
-
-	MTPInputFile vfile;
-	MTPint vduration;
-	MTPint vw;
-	MTPint vh;
-	MTPstring vmime_type;
-	MTPstring vcaption;
-};
-
-class MTPDinputMediaUploadedThumbVideo : public mtpDataImpl<MTPDinputMediaUploadedThumbVideo> {
-public:
-	MTPDinputMediaUploadedThumbVideo() {
-	}
-	MTPDinputMediaUploadedThumbVideo(const MTPInputFile &_file, const MTPInputFile &_thumb, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption) : vfile(_file), vthumb(_thumb), vduration(_duration), vw(_w), vh(_h), vmime_type(_mime_type), vcaption(_caption) {
-	}
-
-	MTPInputFile vfile;
-	MTPInputFile vthumb;
-	MTPint vduration;
-	MTPint vw;
-	MTPint vh;
-	MTPstring vmime_type;
-	MTPstring vcaption;
-};
-
-class MTPDinputMediaVideo : public mtpDataImpl<MTPDinputMediaVideo> {
-public:
-	MTPDinputMediaVideo() {
-	}
-	MTPDinputMediaVideo(const MTPInputVideo &_id, const MTPstring &_caption) : vid(_id), vcaption(_caption) {
-	}
-
-	MTPInputVideo vid;
-	MTPstring vcaption;
-};
-
-class MTPDinputMediaUploadedAudio : public mtpDataImpl<MTPDinputMediaUploadedAudio> {
-public:
-	MTPDinputMediaUploadedAudio() {
-	}
-	MTPDinputMediaUploadedAudio(const MTPInputFile &_file, MTPint _duration, const MTPstring &_mime_type) : vfile(_file), vduration(_duration), vmime_type(_mime_type) {
-	}
-
-	MTPInputFile vfile;
-	MTPint vduration;
-	MTPstring vmime_type;
-};
-
-class MTPDinputMediaAudio : public mtpDataImpl<MTPDinputMediaAudio> {
-public:
-	MTPDinputMediaAudio() {
-	}
-	MTPDinputMediaAudio(const MTPInputAudio &_id) : vid(_id) {
-	}
-
-	MTPInputAudio vid;
-};
-
 class MTPDinputMediaUploadedDocument : public mtpDataImpl<MTPDinputMediaUploadedDocument> {
 public:
 	MTPDinputMediaUploadedDocument() {
@@ -9979,17 +9521,6 @@ public:
 	MTPlong vaccess_hash;
 };
 
-class MTPDinputVideo : public mtpDataImpl<MTPDinputVideo> {
-public:
-	MTPDinputVideo() {
-	}
-	MTPDinputVideo(const MTPlong &_id, const MTPlong &_access_hash) : vid(_id), vaccess_hash(_access_hash) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-};
-
 class MTPDinputFileLocation : public mtpDataImpl<MTPDinputFileLocation> {
 public:
 	MTPDinputFileLocation() {
@@ -10002,17 +9533,6 @@ public:
 	MTPlong vsecret;
 };
 
-class MTPDinputVideoFileLocation : public mtpDataImpl<MTPDinputVideoFileLocation> {
-public:
-	MTPDinputVideoFileLocation() {
-	}
-	MTPDinputVideoFileLocation(const MTPlong &_id, const MTPlong &_access_hash) : vid(_id), vaccess_hash(_access_hash) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-};
-
 class MTPDinputEncryptedFileLocation : public mtpDataImpl<MTPDinputEncryptedFileLocation> {
 public:
 	MTPDinputEncryptedFileLocation() {
@@ -10024,17 +9544,6 @@ public:
 	MTPlong vaccess_hash;
 };
 
-class MTPDinputAudioFileLocation : public mtpDataImpl<MTPDinputAudioFileLocation> {
-public:
-	MTPDinputAudioFileLocation() {
-	}
-	MTPDinputAudioFileLocation(const MTPlong &_id, const MTPlong &_access_hash) : vid(_id), vaccess_hash(_access_hash) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-};
-
 class MTPDinputDocumentFileLocation : public mtpDataImpl<MTPDinputDocumentFileLocation> {
 public:
 	MTPDinputDocumentFileLocation() {
@@ -10314,6 +9823,7 @@ public:
 		flag_verified = (1 << 7),
 		flag_megagroup = (1 << 8),
 		flag_restricted = (1 << 9),
+		flag_invites_enabled = (1 << 10),
 		flag_username = (1 << 6),
 		flag_restriction_reason = (1 << 9),
 	};
@@ -10327,6 +9837,7 @@ public:
 	bool is_verified() const { return vflags.v & flag_verified; }
 	bool is_megagroup() const { return vflags.v & flag_megagroup; }
 	bool is_restricted() const { return vflags.v & flag_restricted; }
+	bool is_invites_enabled() const { return vflags.v & flag_invites_enabled; }
 	bool has_username() const { return vflags.v & flag_username; }
 	bool has_restriction_reason() const { return vflags.v & flag_restriction_reason; }
 };
@@ -10576,17 +10087,6 @@ public:
 	MTPstring vcaption;
 };
 
-class MTPDmessageMediaVideo : public mtpDataImpl<MTPDmessageMediaVideo> {
-public:
-	MTPDmessageMediaVideo() {
-	}
-	MTPDmessageMediaVideo(const MTPVideo &_video, const MTPstring &_caption) : vvideo(_video), vcaption(_caption) {
-	}
-
-	MTPVideo vvideo;
-	MTPstring vcaption;
-};
-
 class MTPDmessageMediaGeo : public mtpDataImpl<MTPDmessageMediaGeo> {
 public:
 	MTPDmessageMediaGeo() {
@@ -10621,16 +10121,6 @@ public:
 	MTPstring vcaption;
 };
 
-class MTPDmessageMediaAudio : public mtpDataImpl<MTPDmessageMediaAudio> {
-public:
-	MTPDmessageMediaAudio() {
-	}
-	MTPDmessageMediaAudio(const MTPAudio &_audio) : vaudio(_audio) {
-	}
-
-	MTPAudio vaudio;
-};
-
 class MTPDmessageMediaWebPage : public mtpDataImpl<MTPDmessageMediaWebPage> {
 public:
 	MTPDmessageMediaWebPage() {
@@ -10839,35 +10329,6 @@ public:
 	MTPbytes vbytes;
 };
 
-class MTPDvideoEmpty : public mtpDataImpl<MTPDvideoEmpty> {
-public:
-	MTPDvideoEmpty() {
-	}
-	MTPDvideoEmpty(const MTPlong &_id) : vid(_id) {
-	}
-
-	MTPlong vid;
-};
-
-class MTPDvideo : public mtpDataImpl<MTPDvideo> {
-public:
-	MTPDvideo() {
-	}
-	MTPDvideo(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _w, MTPint _h) : vid(_id), vaccess_hash(_access_hash), vdate(_date), vduration(_duration), vmime_type(_mime_type), vsize(_size), vthumb(_thumb), vdc_id(_dc_id), vw(_w), vh(_h) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-	MTPint vdate;
-	MTPint vduration;
-	MTPstring vmime_type;
-	MTPint vsize;
-	MTPPhotoSize vthumb;
-	MTPint vdc_id;
-	MTPint vw;
-	MTPint vh;
-};
-
 class MTPDgeoPoint : public mtpDataImpl<MTPDgeoPoint> {
 public:
 	MTPDgeoPoint() {
@@ -11056,17 +10517,6 @@ public:
 	MTPint vdate;
 };
 
-class MTPDcontactSuggested : public mtpDataImpl<MTPDcontactSuggested> {
-public:
-	MTPDcontactSuggested() {
-	}
-	MTPDcontactSuggested(MTPint _user_id, MTPint _mutual_contacts) : vuser_id(_user_id), vmutual_contacts(_mutual_contacts) {
-	}
-
-	MTPint vuser_id;
-	MTPint vmutual_contacts;
-};
-
 class MTPDcontactStatus : public mtpDataImpl<MTPDcontactStatus> {
 public:
 	MTPDcontactStatus() {
@@ -11136,17 +10586,6 @@ public:
 	MTPVector<MTPUser> vusers;
 };
 
-class MTPDcontacts_suggested : public mtpDataImpl<MTPDcontacts_suggested> {
-public:
-	MTPDcontacts_suggested() {
-	}
-	MTPDcontacts_suggested(const MTPVector<MTPContactSuggested> &_results, const MTPVector<MTPUser> &_users) : vresults(_results), vusers(_users) {
-	}
-
-	MTPVector<MTPContactSuggested> vresults;
-	MTPVector<MTPUser> vusers;
-};
-
 class MTPDmessages_dialogs : public mtpDataImpl<MTPDmessages_dialogs> {
 public:
 	MTPDmessages_dialogs() {
@@ -11720,6 +11159,18 @@ public:
 	MTPstring voffset;
 };
 
+class MTPDupdateBotInlineSend : public mtpDataImpl<MTPDupdateBotInlineSend> {
+public:
+	MTPDupdateBotInlineSend() {
+	}
+	MTPDupdateBotInlineSend(MTPint _user_id, const MTPstring &_query, const MTPstring &_id) : vuser_id(_user_id), vquery(_query), vid(_id) {
+	}
+
+	MTPint vuser_id;
+	MTPstring vquery;
+	MTPstring vid;
+};
+
 class MTPDupdates_state : public mtpDataImpl<MTPDupdates_state> {
 public:
 	MTPDupdates_state() {
@@ -12257,17 +11708,6 @@ public:
 	MTPEncryptedFile vfile;
 };
 
-class MTPDinputAudio : public mtpDataImpl<MTPDinputAudio> {
-public:
-	MTPDinputAudio() {
-	}
-	MTPDinputAudio(const MTPlong &_id, const MTPlong &_access_hash) : vid(_id), vaccess_hash(_access_hash) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-};
-
 class MTPDinputDocument : public mtpDataImpl<MTPDinputDocument> {
 public:
 	MTPDinputDocument() {
@@ -12279,32 +11719,6 @@ public:
 	MTPlong vaccess_hash;
 };
 
-class MTPDaudioEmpty : public mtpDataImpl<MTPDaudioEmpty> {
-public:
-	MTPDaudioEmpty() {
-	}
-	MTPDaudioEmpty(const MTPlong &_id) : vid(_id) {
-	}
-
-	MTPlong vid;
-};
-
-class MTPDaudio : public mtpDataImpl<MTPDaudio> {
-public:
-	MTPDaudio() {
-	}
-	MTPDaudio(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, MTPint _dc_id) : vid(_id), vaccess_hash(_access_hash), vdate(_date), vduration(_duration), vmime_type(_mime_type), vsize(_size), vdc_id(_dc_id) {
-	}
-
-	MTPlong vid;
-	MTPlong vaccess_hash;
-	MTPint vdate;
-	MTPint vduration;
-	MTPstring vmime_type;
-	MTPint vsize;
-	MTPint vdc_id;
-};
-
 class MTPDdocumentEmpty : public mtpDataImpl<MTPDdocumentEmpty> {
 public:
 	MTPDdocumentEmpty() {
@@ -12515,12 +11929,26 @@ class MTPDdocumentAttributeAudio : public mtpDataImpl<MTPDdocumentAttributeAudio
 public:
 	MTPDdocumentAttributeAudio() {
 	}
-	MTPDdocumentAttributeAudio(MTPint _duration, const MTPstring &_title, const MTPstring &_performer) : vduration(_duration), vtitle(_title), vperformer(_performer) {
+	MTPDdocumentAttributeAudio(MTPint _flags, MTPint _duration, const MTPstring &_title, const MTPstring &_performer, const MTPbytes &_waveform) : vflags(_flags), vduration(_duration), vtitle(_title), vperformer(_performer), vwaveform(_waveform) {
 	}
 
+	MTPint vflags;
 	MTPint vduration;
 	MTPstring vtitle;
 	MTPstring vperformer;
+	MTPbytes vwaveform;
+
+	enum {
+		flag_voice = (1 << 10),
+		flag_title = (1 << 0),
+		flag_performer = (1 << 1),
+		flag_waveform = (1 << 2),
+	};
+
+	bool is_voice() const { return vflags.v & flag_voice; }
+	bool has_title() const { return vflags.v & flag_title; }
+	bool has_performer() const { return vflags.v & flag_performer; }
+	bool has_waveform() const { return vflags.v & flag_waveform; }
 };
 
 class MTPDdocumentAttributeFilename : public mtpDataImpl<MTPDdocumentAttributeFilename> {
@@ -15991,45 +15419,6 @@ public:
 	}
 };
 
-class MTPcontacts_getSuggested { // RPC method 'contacts.getSuggested'
-public:
-	MTPint vlimit;
-
-	MTPcontacts_getSuggested() {
-	}
-	MTPcontacts_getSuggested(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_getSuggested) {
-		read(from, end, cons);
-	}
-	MTPcontacts_getSuggested(MTPint _limit) : vlimit(_limit) {
-	}
-
-	uint32 innerLength() const {
-		return vlimit.innerLength();
-	}
-	mtpTypeId type() const {
-		return mtpc_contacts_getSuggested;
-	}
-	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_getSuggested) {
-		vlimit.read(from, end);
-	}
-	void write(mtpBuffer &to) const {
-		vlimit.write(to);
-	}
-
-	typedef MTPcontacts_Suggested ResponseType;
-};
-class MTPcontacts_GetSuggested : public MTPBoxed<MTPcontacts_getSuggested> {
-public:
-	MTPcontacts_GetSuggested() {
-	}
-	MTPcontacts_GetSuggested(const MTPcontacts_getSuggested &v) : MTPBoxed<MTPcontacts_getSuggested>(v) {
-	}
-	MTPcontacts_GetSuggested(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPcontacts_getSuggested>(from, end, cons) {
-	}
-	MTPcontacts_GetSuggested(MTPint _limit) : MTPBoxed<MTPcontacts_getSuggested>(MTPcontacts_getSuggested(_limit)) {
-	}
-};
-
 class MTPcontacts_deleteContact { // RPC method 'contacts.deleteContact'
 public:
 	MTPInputUser vid;
@@ -16470,6 +15859,7 @@ class MTPmessages_getHistory { // RPC method 'messages.getHistory'
 public:
 	MTPInputPeer vpeer;
 	MTPint voffset_id;
+	MTPint voffset_date;
 	MTPint vadd_offset;
 	MTPint vlimit;
 	MTPint vmax_id;
@@ -16480,11 +15870,11 @@ public:
 	MTPmessages_getHistory(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getHistory) {
 		read(from, end, cons);
 	}
-	MTPmessages_getHistory(const MTPInputPeer &_peer, MTPint _offset_id, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : vpeer(_peer), voffset_id(_offset_id), vadd_offset(_add_offset), vlimit(_limit), vmax_id(_max_id), vmin_id(_min_id) {
+	MTPmessages_getHistory(const MTPInputPeer &_peer, MTPint _offset_id, MTPint _offset_date, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : vpeer(_peer), voffset_id(_offset_id), voffset_date(_offset_date), vadd_offset(_add_offset), vlimit(_limit), vmax_id(_max_id), vmin_id(_min_id) {
 	}
 
 	uint32 innerLength() const {
-		return vpeer.innerLength() + voffset_id.innerLength() + vadd_offset.innerLength() + vlimit.innerLength() + vmax_id.innerLength() + vmin_id.innerLength();
+		return vpeer.innerLength() + voffset_id.innerLength() + voffset_date.innerLength() + vadd_offset.innerLength() + vlimit.innerLength() + vmax_id.innerLength() + vmin_id.innerLength();
 	}
 	mtpTypeId type() const {
 		return mtpc_messages_getHistory;
@@ -16492,6 +15882,7 @@ public:
 	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getHistory) {
 		vpeer.read(from, end);
 		voffset_id.read(from, end);
+		voffset_date.read(from, end);
 		vadd_offset.read(from, end);
 		vlimit.read(from, end);
 		vmax_id.read(from, end);
@@ -16500,6 +15891,7 @@ public:
 	void write(mtpBuffer &to) const {
 		vpeer.write(to);
 		voffset_id.write(to);
+		voffset_date.write(to);
 		vadd_offset.write(to);
 		vlimit.write(to);
 		vmax_id.write(to);
@@ -16516,7 +15908,7 @@ public:
 	}
 	MTPmessages_GetHistory(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPmessages_getHistory>(from, end, cons) {
 	}
-	MTPmessages_GetHistory(const MTPInputPeer &_peer, MTPint _offset_id, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : MTPBoxed<MTPmessages_getHistory>(MTPmessages_getHistory(_peer, _offset_id, _add_offset, _limit, _max_id, _min_id)) {
+	MTPmessages_GetHistory(const MTPInputPeer &_peer, MTPint _offset_id, MTPint _offset_date, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : MTPBoxed<MTPmessages_getHistory>(MTPmessages_getHistory(_peer, _offset_id, _offset_date, _add_offset, _limit, _max_id, _min_id)) {
 	}
 };
 
@@ -19672,6 +19064,7 @@ class MTPchannels_getImportantHistory { // RPC method 'channels.getImportantHist
 public:
 	MTPInputChannel vchannel;
 	MTPint voffset_id;
+	MTPint voffset_date;
 	MTPint vadd_offset;
 	MTPint vlimit;
 	MTPint vmax_id;
@@ -19682,11 +19075,11 @@ public:
 	MTPchannels_getImportantHistory(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_getImportantHistory) {
 		read(from, end, cons);
 	}
-	MTPchannels_getImportantHistory(const MTPInputChannel &_channel, MTPint _offset_id, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : vchannel(_channel), voffset_id(_offset_id), vadd_offset(_add_offset), vlimit(_limit), vmax_id(_max_id), vmin_id(_min_id) {
+	MTPchannels_getImportantHistory(const MTPInputChannel &_channel, MTPint _offset_id, MTPint _offset_date, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : vchannel(_channel), voffset_id(_offset_id), voffset_date(_offset_date), vadd_offset(_add_offset), vlimit(_limit), vmax_id(_max_id), vmin_id(_min_id) {
 	}
 
 	uint32 innerLength() const {
-		return vchannel.innerLength() + voffset_id.innerLength() + vadd_offset.innerLength() + vlimit.innerLength() + vmax_id.innerLength() + vmin_id.innerLength();
+		return vchannel.innerLength() + voffset_id.innerLength() + voffset_date.innerLength() + vadd_offset.innerLength() + vlimit.innerLength() + vmax_id.innerLength() + vmin_id.innerLength();
 	}
 	mtpTypeId type() const {
 		return mtpc_channels_getImportantHistory;
@@ -19694,6 +19087,7 @@ public:
 	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_getImportantHistory) {
 		vchannel.read(from, end);
 		voffset_id.read(from, end);
+		voffset_date.read(from, end);
 		vadd_offset.read(from, end);
 		vlimit.read(from, end);
 		vmax_id.read(from, end);
@@ -19702,6 +19096,7 @@ public:
 	void write(mtpBuffer &to) const {
 		vchannel.write(to);
 		voffset_id.write(to);
+		voffset_date.write(to);
 		vadd_offset.write(to);
 		vlimit.write(to);
 		vmax_id.write(to);
@@ -19718,7 +19113,7 @@ public:
 	}
 	MTPchannels_GetImportantHistory(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPchannels_getImportantHistory>(from, end, cons) {
 	}
-	MTPchannels_GetImportantHistory(const MTPInputChannel &_channel, MTPint _offset_id, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : MTPBoxed<MTPchannels_getImportantHistory>(MTPchannels_getImportantHistory(_channel, _offset_id, _add_offset, _limit, _max_id, _min_id)) {
+	MTPchannels_GetImportantHistory(const MTPInputChannel &_channel, MTPint _offset_id, MTPint _offset_date, MTPint _add_offset, MTPint _limit, MTPint _max_id, MTPint _min_id) : MTPBoxed<MTPchannels_getImportantHistory>(MTPchannels_getImportantHistory(_channel, _offset_id, _offset_date, _add_offset, _limit, _max_id, _min_id)) {
 	}
 };
 
@@ -20696,6 +20091,48 @@ public:
 	}
 };
 
+class MTPchannels_toggleInvites { // RPC method 'channels.toggleInvites'
+public:
+	MTPInputChannel vchannel;
+	MTPBool venabled;
+
+	MTPchannels_toggleInvites() {
+	}
+	MTPchannels_toggleInvites(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_toggleInvites) {
+		read(from, end, cons);
+	}
+	MTPchannels_toggleInvites(const MTPInputChannel &_channel, MTPBool _enabled) : vchannel(_channel), venabled(_enabled) {
+	}
+
+	uint32 innerLength() const {
+		return vchannel.innerLength() + venabled.innerLength();
+	}
+	mtpTypeId type() const {
+		return mtpc_channels_toggleInvites;
+	}
+	void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_toggleInvites) {
+		vchannel.read(from, end);
+		venabled.read(from, end);
+	}
+	void write(mtpBuffer &to) const {
+		vchannel.write(to);
+		venabled.write(to);
+	}
+
+	typedef MTPUpdates ResponseType;
+};
+class MTPchannels_ToggleInvites : public MTPBoxed<MTPchannels_toggleInvites> {
+public:
+	MTPchannels_ToggleInvites() {
+	}
+	MTPchannels_ToggleInvites(const MTPchannels_toggleInvites &v) : MTPBoxed<MTPchannels_toggleInvites>(v) {
+	}
+	MTPchannels_ToggleInvites(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPchannels_toggleInvites>(from, end, cons) {
+	}
+	MTPchannels_ToggleInvites(const MTPInputChannel &_channel, MTPBool _enabled) : MTPBoxed<MTPchannels_toggleInvites>(MTPchannels_toggleInvites(_channel, _enabled)) {
+	}
+};
+
 // Inline methods definition
 
 inline MTPresPQ::MTPresPQ() : mtpDataOwner(new MTPDresPQ()) {
@@ -21956,26 +21393,6 @@ inline uint32 MTPinputMedia::innerLength() const {
 			const MTPDinputMediaContact &v(c_inputMediaContact());
 			return v.vphone_number.innerLength() + v.vfirst_name.innerLength() + v.vlast_name.innerLength();
 		}
-		case mtpc_inputMediaUploadedVideo: {
-			const MTPDinputMediaUploadedVideo &v(c_inputMediaUploadedVideo());
-			return v.vfile.innerLength() + v.vduration.innerLength() + v.vw.innerLength() + v.vh.innerLength() + v.vmime_type.innerLength() + v.vcaption.innerLength();
-		}
-		case mtpc_inputMediaUploadedThumbVideo: {
-			const MTPDinputMediaUploadedThumbVideo &v(c_inputMediaUploadedThumbVideo());
-			return v.vfile.innerLength() + v.vthumb.innerLength() + v.vduration.innerLength() + v.vw.innerLength() + v.vh.innerLength() + v.vmime_type.innerLength() + v.vcaption.innerLength();
-		}
-		case mtpc_inputMediaVideo: {
-			const MTPDinputMediaVideo &v(c_inputMediaVideo());
-			return v.vid.innerLength() + v.vcaption.innerLength();
-		}
-		case mtpc_inputMediaUploadedAudio: {
-			const MTPDinputMediaUploadedAudio &v(c_inputMediaUploadedAudio());
-			return v.vfile.innerLength() + v.vduration.innerLength() + v.vmime_type.innerLength();
-		}
-		case mtpc_inputMediaAudio: {
-			const MTPDinputMediaAudio &v(c_inputMediaAudio());
-			return v.vid.innerLength();
-		}
 		case mtpc_inputMediaUploadedDocument: {
 			const MTPDinputMediaUploadedDocument &v(c_inputMediaUploadedDocument());
 			return v.vfile.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength();
@@ -22031,45 +21448,6 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT
 			v.vfirst_name.read(from, end);
 			v.vlast_name.read(from, end);
 		} break;
-		case mtpc_inputMediaUploadedVideo: _type = cons; {
-			if (!data) setData(new MTPDinputMediaUploadedVideo());
-			MTPDinputMediaUploadedVideo &v(_inputMediaUploadedVideo());
-			v.vfile.read(from, end);
-			v.vduration.read(from, end);
-			v.vw.read(from, end);
-			v.vh.read(from, end);
-			v.vmime_type.read(from, end);
-			v.vcaption.read(from, end);
-		} break;
-		case mtpc_inputMediaUploadedThumbVideo: _type = cons; {
-			if (!data) setData(new MTPDinputMediaUploadedThumbVideo());
-			MTPDinputMediaUploadedThumbVideo &v(_inputMediaUploadedThumbVideo());
-			v.vfile.read(from, end);
-			v.vthumb.read(from, end);
-			v.vduration.read(from, end);
-			v.vw.read(from, end);
-			v.vh.read(from, end);
-			v.vmime_type.read(from, end);
-			v.vcaption.read(from, end);
-		} break;
-		case mtpc_inputMediaVideo: _type = cons; {
-			if (!data) setData(new MTPDinputMediaVideo());
-			MTPDinputMediaVideo &v(_inputMediaVideo());
-			v.vid.read(from, end);
-			v.vcaption.read(from, end);
-		} break;
-		case mtpc_inputMediaUploadedAudio: _type = cons; {
-			if (!data) setData(new MTPDinputMediaUploadedAudio());
-			MTPDinputMediaUploadedAudio &v(_inputMediaUploadedAudio());
-			v.vfile.read(from, end);
-			v.vduration.read(from, end);
-			v.vmime_type.read(from, end);
-		} break;
-		case mtpc_inputMediaAudio: _type = cons; {
-			if (!data) setData(new MTPDinputMediaAudio());
-			MTPDinputMediaAudio &v(_inputMediaAudio());
-			v.vid.read(from, end);
-		} break;
 		case mtpc_inputMediaUploadedDocument: _type = cons; {
 			if (!data) setData(new MTPDinputMediaUploadedDocument());
 			MTPDinputMediaUploadedDocument &v(_inputMediaUploadedDocument());
@@ -22133,40 +21511,6 @@ inline void MTPinputMedia::write(mtpBuffer &to) const {
 			v.vfirst_name.write(to);
 			v.vlast_name.write(to);
 		} break;
-		case mtpc_inputMediaUploadedVideo: {
-			const MTPDinputMediaUploadedVideo &v(c_inputMediaUploadedVideo());
-			v.vfile.write(to);
-			v.vduration.write(to);
-			v.vw.write(to);
-			v.vh.write(to);
-			v.vmime_type.write(to);
-			v.vcaption.write(to);
-		} break;
-		case mtpc_inputMediaUploadedThumbVideo: {
-			const MTPDinputMediaUploadedThumbVideo &v(c_inputMediaUploadedThumbVideo());
-			v.vfile.write(to);
-			v.vthumb.write(to);
-			v.vduration.write(to);
-			v.vw.write(to);
-			v.vh.write(to);
-			v.vmime_type.write(to);
-			v.vcaption.write(to);
-		} break;
-		case mtpc_inputMediaVideo: {
-			const MTPDinputMediaVideo &v(c_inputMediaVideo());
-			v.vid.write(to);
-			v.vcaption.write(to);
-		} break;
-		case mtpc_inputMediaUploadedAudio: {
-			const MTPDinputMediaUploadedAudio &v(c_inputMediaUploadedAudio());
-			v.vfile.write(to);
-			v.vduration.write(to);
-			v.vmime_type.write(to);
-		} break;
-		case mtpc_inputMediaAudio: {
-			const MTPDinputMediaAudio &v(c_inputMediaAudio());
-			v.vid.write(to);
-		} break;
 		case mtpc_inputMediaUploadedDocument: {
 			const MTPDinputMediaUploadedDocument &v(c_inputMediaUploadedDocument());
 			v.vfile.write(to);
@@ -22209,11 +21553,6 @@ inline MTPinputMedia::MTPinputMedia(mtpTypeId type) : mtpDataOwner(0), _type(typ
 		case mtpc_inputMediaPhoto: setData(new MTPDinputMediaPhoto()); break;
 		case mtpc_inputMediaGeoPoint: setData(new MTPDinputMediaGeoPoint()); break;
 		case mtpc_inputMediaContact: setData(new MTPDinputMediaContact()); break;
-		case mtpc_inputMediaUploadedVideo: setData(new MTPDinputMediaUploadedVideo()); break;
-		case mtpc_inputMediaUploadedThumbVideo: setData(new MTPDinputMediaUploadedThumbVideo()); break;
-		case mtpc_inputMediaVideo: setData(new MTPDinputMediaVideo()); break;
-		case mtpc_inputMediaUploadedAudio: setData(new MTPDinputMediaUploadedAudio()); break;
-		case mtpc_inputMediaAudio: setData(new MTPDinputMediaAudio()); break;
 		case mtpc_inputMediaUploadedDocument: setData(new MTPDinputMediaUploadedDocument()); break;
 		case mtpc_inputMediaUploadedThumbDocument: setData(new MTPDinputMediaUploadedThumbDocument()); break;
 		case mtpc_inputMediaDocument: setData(new MTPDinputMediaDocument()); break;
@@ -22230,16 +21569,6 @@ inline MTPinputMedia::MTPinputMedia(MTPDinputMediaGeoPoint *_data) : mtpDataOwne
 }
 inline MTPinputMedia::MTPinputMedia(MTPDinputMediaContact *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaContact) {
 }
-inline MTPinputMedia::MTPinputMedia(MTPDinputMediaUploadedVideo *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaUploadedVideo) {
-}
-inline MTPinputMedia::MTPinputMedia(MTPDinputMediaUploadedThumbVideo *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaUploadedThumbVideo) {
-}
-inline MTPinputMedia::MTPinputMedia(MTPDinputMediaVideo *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaVideo) {
-}
-inline MTPinputMedia::MTPinputMedia(MTPDinputMediaUploadedAudio *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaUploadedAudio) {
-}
-inline MTPinputMedia::MTPinputMedia(MTPDinputMediaAudio *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaAudio) {
-}
 inline MTPinputMedia::MTPinputMedia(MTPDinputMediaUploadedDocument *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaUploadedDocument) {
 }
 inline MTPinputMedia::MTPinputMedia(MTPDinputMediaUploadedThumbDocument *_data) : mtpDataOwner(_data), _type(mtpc_inputMediaUploadedThumbDocument) {
@@ -22265,21 +21594,6 @@ inline MTPinputMedia MTP_inputMediaGeoPoint(const MTPInputGeoPoint &_geo_point)
 inline MTPinputMedia MTP_inputMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name) {
 	return MTPinputMedia(new MTPDinputMediaContact(_phone_number, _first_name, _last_name));
 }
-inline MTPinputMedia MTP_inputMediaUploadedVideo(const MTPInputFile &_file, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption) {
-	return MTPinputMedia(new MTPDinputMediaUploadedVideo(_file, _duration, _w, _h, _mime_type, _caption));
-}
-inline MTPinputMedia MTP_inputMediaUploadedThumbVideo(const MTPInputFile &_file, const MTPInputFile &_thumb, MTPint _duration, MTPint _w, MTPint _h, const MTPstring &_mime_type, const MTPstring &_caption) {
-	return MTPinputMedia(new MTPDinputMediaUploadedThumbVideo(_file, _thumb, _duration, _w, _h, _mime_type, _caption));
-}
-inline MTPinputMedia MTP_inputMediaVideo(const MTPInputVideo &_id, const MTPstring &_caption) {
-	return MTPinputMedia(new MTPDinputMediaVideo(_id, _caption));
-}
-inline MTPinputMedia MTP_inputMediaUploadedAudio(const MTPInputFile &_file, MTPint _duration, const MTPstring &_mime_type) {
-	return MTPinputMedia(new MTPDinputMediaUploadedAudio(_file, _duration, _mime_type));
-}
-inline MTPinputMedia MTP_inputMediaAudio(const MTPInputAudio &_id) {
-	return MTPinputMedia(new MTPDinputMediaAudio(_id));
-}
 inline MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector<MTPDocumentAttribute> &_attributes, const MTPstring &_caption) {
 	return MTPinputMedia(new MTPDinputMediaUploadedDocument(_file, _mime_type, _attributes, _caption));
 }
@@ -22470,75 +21784,16 @@ inline MTPinputPhoto MTP_inputPhoto(const MTPlong &_id, const MTPlong &_access_h
 	return MTPinputPhoto(new MTPDinputPhoto(_id, _access_hash));
 }
 
-inline uint32 MTPinputVideo::innerLength() const {
-	switch (_type) {
-		case mtpc_inputVideo: {
-			const MTPDinputVideo &v(c_inputVideo());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength();
-		}
-	}
-	return 0;
-}
-inline mtpTypeId MTPinputVideo::type() const {
-	if (!_type) throw mtpErrorUninitialized();
-	return _type;
-}
-inline void MTPinputVideo::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != _type) setData(0);
-	switch (cons) {
-		case mtpc_inputVideoEmpty: _type = cons; break;
-		case mtpc_inputVideo: _type = cons; {
-			if (!data) setData(new MTPDinputVideo());
-			MTPDinputVideo &v(_inputVideo());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-		} break;
-		default: throw mtpErrorUnexpected(cons, "MTPinputVideo");
-	}
-}
-inline void MTPinputVideo::write(mtpBuffer &to) const {
-	switch (_type) {
-		case mtpc_inputVideo: {
-			const MTPDinputVideo &v(c_inputVideo());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-		} break;
-	}
-}
-inline MTPinputVideo::MTPinputVideo(mtpTypeId type) : mtpDataOwner(0), _type(type) {
-	switch (type) {
-		case mtpc_inputVideoEmpty: break;
-		case mtpc_inputVideo: setData(new MTPDinputVideo()); break;
-		default: throw mtpErrorBadTypeId(type, "MTPinputVideo");
-	}
-}
-inline MTPinputVideo::MTPinputVideo(MTPDinputVideo *_data) : mtpDataOwner(_data), _type(mtpc_inputVideo) {
-}
-inline MTPinputVideo MTP_inputVideoEmpty() {
-	return MTPinputVideo(mtpc_inputVideoEmpty);
-}
-inline MTPinputVideo MTP_inputVideo(const MTPlong &_id, const MTPlong &_access_hash) {
-	return MTPinputVideo(new MTPDinputVideo(_id, _access_hash));
-}
-
 inline uint32 MTPinputFileLocation::innerLength() const {
 	switch (_type) {
 		case mtpc_inputFileLocation: {
 			const MTPDinputFileLocation &v(c_inputFileLocation());
 			return v.vvolume_id.innerLength() + v.vlocal_id.innerLength() + v.vsecret.innerLength();
 		}
-		case mtpc_inputVideoFileLocation: {
-			const MTPDinputVideoFileLocation &v(c_inputVideoFileLocation());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength();
-		}
 		case mtpc_inputEncryptedFileLocation: {
 			const MTPDinputEncryptedFileLocation &v(c_inputEncryptedFileLocation());
 			return v.vid.innerLength() + v.vaccess_hash.innerLength();
 		}
-		case mtpc_inputAudioFileLocation: {
-			const MTPDinputAudioFileLocation &v(c_inputAudioFileLocation());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength();
-		}
 		case mtpc_inputDocumentFileLocation: {
 			const MTPDinputDocumentFileLocation &v(c_inputDocumentFileLocation());
 			return v.vid.innerLength() + v.vaccess_hash.innerLength();
@@ -22560,24 +21815,12 @@ inline void MTPinputFileLocation::read(const mtpPrime *&from, const mtpPrime *en
 			v.vlocal_id.read(from, end);
 			v.vsecret.read(from, end);
 		} break;
-		case mtpc_inputVideoFileLocation: _type = cons; {
-			if (!data) setData(new MTPDinputVideoFileLocation());
-			MTPDinputVideoFileLocation &v(_inputVideoFileLocation());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-		} break;
 		case mtpc_inputEncryptedFileLocation: _type = cons; {
 			if (!data) setData(new MTPDinputEncryptedFileLocation());
 			MTPDinputEncryptedFileLocation &v(_inputEncryptedFileLocation());
 			v.vid.read(from, end);
 			v.vaccess_hash.read(from, end);
 		} break;
-		case mtpc_inputAudioFileLocation: _type = cons; {
-			if (!data) setData(new MTPDinputAudioFileLocation());
-			MTPDinputAudioFileLocation &v(_inputAudioFileLocation());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-		} break;
 		case mtpc_inputDocumentFileLocation: _type = cons; {
 			if (!data) setData(new MTPDinputDocumentFileLocation());
 			MTPDinputDocumentFileLocation &v(_inputDocumentFileLocation());
@@ -22595,21 +21838,11 @@ inline void MTPinputFileLocation::write(mtpBuffer &to) const {
 			v.vlocal_id.write(to);
 			v.vsecret.write(to);
 		} break;
-		case mtpc_inputVideoFileLocation: {
-			const MTPDinputVideoFileLocation &v(c_inputVideoFileLocation());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-		} break;
 		case mtpc_inputEncryptedFileLocation: {
 			const MTPDinputEncryptedFileLocation &v(c_inputEncryptedFileLocation());
 			v.vid.write(to);
 			v.vaccess_hash.write(to);
 		} break;
-		case mtpc_inputAudioFileLocation: {
-			const MTPDinputAudioFileLocation &v(c_inputAudioFileLocation());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-		} break;
 		case mtpc_inputDocumentFileLocation: {
 			const MTPDinputDocumentFileLocation &v(c_inputDocumentFileLocation());
 			v.vid.write(to);
@@ -22620,35 +21853,23 @@ inline void MTPinputFileLocation::write(mtpBuffer &to) const {
 inline MTPinputFileLocation::MTPinputFileLocation(mtpTypeId type) : mtpDataOwner(0), _type(type) {
 	switch (type) {
 		case mtpc_inputFileLocation: setData(new MTPDinputFileLocation()); break;
-		case mtpc_inputVideoFileLocation: setData(new MTPDinputVideoFileLocation()); break;
 		case mtpc_inputEncryptedFileLocation: setData(new MTPDinputEncryptedFileLocation()); break;
-		case mtpc_inputAudioFileLocation: setData(new MTPDinputAudioFileLocation()); break;
 		case mtpc_inputDocumentFileLocation: setData(new MTPDinputDocumentFileLocation()); break;
 		default: throw mtpErrorBadTypeId(type, "MTPinputFileLocation");
 	}
 }
 inline MTPinputFileLocation::MTPinputFileLocation(MTPDinputFileLocation *_data) : mtpDataOwner(_data), _type(mtpc_inputFileLocation) {
 }
-inline MTPinputFileLocation::MTPinputFileLocation(MTPDinputVideoFileLocation *_data) : mtpDataOwner(_data), _type(mtpc_inputVideoFileLocation) {
-}
 inline MTPinputFileLocation::MTPinputFileLocation(MTPDinputEncryptedFileLocation *_data) : mtpDataOwner(_data), _type(mtpc_inputEncryptedFileLocation) {
 }
-inline MTPinputFileLocation::MTPinputFileLocation(MTPDinputAudioFileLocation *_data) : mtpDataOwner(_data), _type(mtpc_inputAudioFileLocation) {
-}
 inline MTPinputFileLocation::MTPinputFileLocation(MTPDinputDocumentFileLocation *_data) : mtpDataOwner(_data), _type(mtpc_inputDocumentFileLocation) {
 }
 inline MTPinputFileLocation MTP_inputFileLocation(const MTPlong &_volume_id, MTPint _local_id, const MTPlong &_secret) {
 	return MTPinputFileLocation(new MTPDinputFileLocation(_volume_id, _local_id, _secret));
 }
-inline MTPinputFileLocation MTP_inputVideoFileLocation(const MTPlong &_id, const MTPlong &_access_hash) {
-	return MTPinputFileLocation(new MTPDinputVideoFileLocation(_id, _access_hash));
-}
 inline MTPinputFileLocation MTP_inputEncryptedFileLocation(const MTPlong &_id, const MTPlong &_access_hash) {
 	return MTPinputFileLocation(new MTPDinputEncryptedFileLocation(_id, _access_hash));
 }
-inline MTPinputFileLocation MTP_inputAudioFileLocation(const MTPlong &_id, const MTPlong &_access_hash) {
-	return MTPinputFileLocation(new MTPDinputAudioFileLocation(_id, _access_hash));
-}
 inline MTPinputFileLocation MTP_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash) {
 	return MTPinputFileLocation(new MTPDinputDocumentFileLocation(_id, _access_hash));
 }
@@ -23780,10 +23001,6 @@ inline uint32 MTPmessageMedia::innerLength() const {
 			const MTPDmessageMediaPhoto &v(c_messageMediaPhoto());
 			return v.vphoto.innerLength() + v.vcaption.innerLength();
 		}
-		case mtpc_messageMediaVideo: {
-			const MTPDmessageMediaVideo &v(c_messageMediaVideo());
-			return v.vvideo.innerLength() + v.vcaption.innerLength();
-		}
 		case mtpc_messageMediaGeo: {
 			const MTPDmessageMediaGeo &v(c_messageMediaGeo());
 			return v.vgeo.innerLength();
@@ -23796,10 +23013,6 @@ inline uint32 MTPmessageMedia::innerLength() const {
 			const MTPDmessageMediaDocument &v(c_messageMediaDocument());
 			return v.vdocument.innerLength() + v.vcaption.innerLength();
 		}
-		case mtpc_messageMediaAudio: {
-			const MTPDmessageMediaAudio &v(c_messageMediaAudio());
-			return v.vaudio.innerLength();
-		}
 		case mtpc_messageMediaWebPage: {
 			const MTPDmessageMediaWebPage &v(c_messageMediaWebPage());
 			return v.vwebpage.innerLength();
@@ -23825,12 +23038,6 @@ inline void MTPmessageMedia::read(const mtpPrime *&from, const mtpPrime *end, mt
 			v.vphoto.read(from, end);
 			v.vcaption.read(from, end);
 		} break;
-		case mtpc_messageMediaVideo: _type = cons; {
-			if (!data) setData(new MTPDmessageMediaVideo());
-			MTPDmessageMediaVideo &v(_messageMediaVideo());
-			v.vvideo.read(from, end);
-			v.vcaption.read(from, end);
-		} break;
 		case mtpc_messageMediaGeo: _type = cons; {
 			if (!data) setData(new MTPDmessageMediaGeo());
 			MTPDmessageMediaGeo &v(_messageMediaGeo());
@@ -23851,11 +23058,6 @@ inline void MTPmessageMedia::read(const mtpPrime *&from, const mtpPrime *end, mt
 			v.vdocument.read(from, end);
 			v.vcaption.read(from, end);
 		} break;
-		case mtpc_messageMediaAudio: _type = cons; {
-			if (!data) setData(new MTPDmessageMediaAudio());
-			MTPDmessageMediaAudio &v(_messageMediaAudio());
-			v.vaudio.read(from, end);
-		} break;
 		case mtpc_messageMediaWebPage: _type = cons; {
 			if (!data) setData(new MTPDmessageMediaWebPage());
 			MTPDmessageMediaWebPage &v(_messageMediaWebPage());
@@ -23880,11 +23082,6 @@ inline void MTPmessageMedia::write(mtpBuffer &to) const {
 			v.vphoto.write(to);
 			v.vcaption.write(to);
 		} break;
-		case mtpc_messageMediaVideo: {
-			const MTPDmessageMediaVideo &v(c_messageMediaVideo());
-			v.vvideo.write(to);
-			v.vcaption.write(to);
-		} break;
 		case mtpc_messageMediaGeo: {
 			const MTPDmessageMediaGeo &v(c_messageMediaGeo());
 			v.vgeo.write(to);
@@ -23901,10 +23098,6 @@ inline void MTPmessageMedia::write(mtpBuffer &to) const {
 			v.vdocument.write(to);
 			v.vcaption.write(to);
 		} break;
-		case mtpc_messageMediaAudio: {
-			const MTPDmessageMediaAudio &v(c_messageMediaAudio());
-			v.vaudio.write(to);
-		} break;
 		case mtpc_messageMediaWebPage: {
 			const MTPDmessageMediaWebPage &v(c_messageMediaWebPage());
 			v.vwebpage.write(to);
@@ -23923,12 +23116,10 @@ inline MTPmessageMedia::MTPmessageMedia(mtpTypeId type) : mtpDataOwner(0), _type
 	switch (type) {
 		case mtpc_messageMediaEmpty: break;
 		case mtpc_messageMediaPhoto: setData(new MTPDmessageMediaPhoto()); break;
-		case mtpc_messageMediaVideo: setData(new MTPDmessageMediaVideo()); break;
 		case mtpc_messageMediaGeo: setData(new MTPDmessageMediaGeo()); break;
 		case mtpc_messageMediaContact: setData(new MTPDmessageMediaContact()); break;
 		case mtpc_messageMediaUnsupported: break;
 		case mtpc_messageMediaDocument: setData(new MTPDmessageMediaDocument()); break;
-		case mtpc_messageMediaAudio: setData(new MTPDmessageMediaAudio()); break;
 		case mtpc_messageMediaWebPage: setData(new MTPDmessageMediaWebPage()); break;
 		case mtpc_messageMediaVenue: setData(new MTPDmessageMediaVenue()); break;
 		default: throw mtpErrorBadTypeId(type, "MTPmessageMedia");
@@ -23936,16 +23127,12 @@ inline MTPmessageMedia::MTPmessageMedia(mtpTypeId type) : mtpDataOwner(0), _type
 }
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaPhoto *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaPhoto) {
 }
-inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaVideo *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaVideo) {
-}
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaGeo *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaGeo) {
 }
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaContact *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaContact) {
 }
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaDocument *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaDocument) {
 }
-inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaAudio *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaAudio) {
-}
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaWebPage *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaWebPage) {
 }
 inline MTPmessageMedia::MTPmessageMedia(MTPDmessageMediaVenue *_data) : mtpDataOwner(_data), _type(mtpc_messageMediaVenue) {
@@ -23956,9 +23143,6 @@ inline MTPmessageMedia MTP_messageMediaEmpty() {
 inline MTPmessageMedia MTP_messageMediaPhoto(const MTPPhoto &_photo, const MTPstring &_caption) {
 	return MTPmessageMedia(new MTPDmessageMediaPhoto(_photo, _caption));
 }
-inline MTPmessageMedia MTP_messageMediaVideo(const MTPVideo &_video, const MTPstring &_caption) {
-	return MTPmessageMedia(new MTPDmessageMediaVideo(_video, _caption));
-}
 inline MTPmessageMedia MTP_messageMediaGeo(const MTPGeoPoint &_geo) {
 	return MTPmessageMedia(new MTPDmessageMediaGeo(_geo));
 }
@@ -23971,9 +23155,6 @@ inline MTPmessageMedia MTP_messageMediaUnsupported() {
 inline MTPmessageMedia MTP_messageMediaDocument(const MTPDocument &_document, const MTPstring &_caption) {
 	return MTPmessageMedia(new MTPDmessageMediaDocument(_document, _caption));
 }
-inline MTPmessageMedia MTP_messageMediaAudio(const MTPAudio &_audio) {
-	return MTPmessageMedia(new MTPDmessageMediaAudio(_audio));
-}
 inline MTPmessageMedia MTP_messageMediaWebPage(const MTPWebPage &_webpage) {
 	return MTPmessageMedia(new MTPDmessageMediaWebPage(_webpage));
 }
@@ -24443,87 +23624,6 @@ inline MTPphotoSize MTP_photoCachedSize(const MTPstring &_type, const MTPFileLoc
 	return MTPphotoSize(new MTPDphotoCachedSize(_type, _location, _w, _h, _bytes));
 }
 
-inline uint32 MTPvideo::innerLength() const {
-	switch (_type) {
-		case mtpc_videoEmpty: {
-			const MTPDvideoEmpty &v(c_videoEmpty());
-			return v.vid.innerLength();
-		}
-		case mtpc_video: {
-			const MTPDvideo &v(c_video());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vduration.innerLength() + v.vmime_type.innerLength() + v.vsize.innerLength() + v.vthumb.innerLength() + v.vdc_id.innerLength() + v.vw.innerLength() + v.vh.innerLength();
-		}
-	}
-	return 0;
-}
-inline mtpTypeId MTPvideo::type() const {
-	if (!_type) throw mtpErrorUninitialized();
-	return _type;
-}
-inline void MTPvideo::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != _type) setData(0);
-	switch (cons) {
-		case mtpc_videoEmpty: _type = cons; {
-			if (!data) setData(new MTPDvideoEmpty());
-			MTPDvideoEmpty &v(_videoEmpty());
-			v.vid.read(from, end);
-		} break;
-		case mtpc_video: _type = cons; {
-			if (!data) setData(new MTPDvideo());
-			MTPDvideo &v(_video());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-			v.vdate.read(from, end);
-			v.vduration.read(from, end);
-			v.vmime_type.read(from, end);
-			v.vsize.read(from, end);
-			v.vthumb.read(from, end);
-			v.vdc_id.read(from, end);
-			v.vw.read(from, end);
-			v.vh.read(from, end);
-		} break;
-		default: throw mtpErrorUnexpected(cons, "MTPvideo");
-	}
-}
-inline void MTPvideo::write(mtpBuffer &to) const {
-	switch (_type) {
-		case mtpc_videoEmpty: {
-			const MTPDvideoEmpty &v(c_videoEmpty());
-			v.vid.write(to);
-		} break;
-		case mtpc_video: {
-			const MTPDvideo &v(c_video());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-			v.vdate.write(to);
-			v.vduration.write(to);
-			v.vmime_type.write(to);
-			v.vsize.write(to);
-			v.vthumb.write(to);
-			v.vdc_id.write(to);
-			v.vw.write(to);
-			v.vh.write(to);
-		} break;
-	}
-}
-inline MTPvideo::MTPvideo(mtpTypeId type) : mtpDataOwner(0), _type(type) {
-	switch (type) {
-		case mtpc_videoEmpty: setData(new MTPDvideoEmpty()); break;
-		case mtpc_video: setData(new MTPDvideo()); break;
-		default: throw mtpErrorBadTypeId(type, "MTPvideo");
-	}
-}
-inline MTPvideo::MTPvideo(MTPDvideoEmpty *_data) : mtpDataOwner(_data), _type(mtpc_videoEmpty) {
-}
-inline MTPvideo::MTPvideo(MTPDvideo *_data) : mtpDataOwner(_data), _type(mtpc_video) {
-}
-inline MTPvideo MTP_videoEmpty(const MTPlong &_id) {
-	return MTPvideo(new MTPDvideoEmpty(_id));
-}
-inline MTPvideo MTP_video(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, const MTPPhotoSize &_thumb, MTPint _dc_id, MTPint _w, MTPint _h) {
-	return MTPvideo(new MTPDvideo(_id, _access_hash, _date, _duration, _mime_type, _size, _thumb, _dc_id, _w, _h));
-}
-
 inline uint32 MTPgeoPoint::innerLength() const {
 	switch (_type) {
 		case mtpc_geoPoint: {
@@ -25202,35 +24302,6 @@ inline MTPcontactBlocked MTP_contactBlocked(MTPint _user_id, MTPint _date) {
 	return MTPcontactBlocked(new MTPDcontactBlocked(_user_id, _date));
 }
 
-inline MTPcontactSuggested::MTPcontactSuggested() : mtpDataOwner(new MTPDcontactSuggested()) {
-}
-
-inline uint32 MTPcontactSuggested::innerLength() const {
-	const MTPDcontactSuggested &v(c_contactSuggested());
-	return v.vuser_id.innerLength() + v.vmutual_contacts.innerLength();
-}
-inline mtpTypeId MTPcontactSuggested::type() const {
-	return mtpc_contactSuggested;
-}
-inline void MTPcontactSuggested::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != mtpc_contactSuggested) throw mtpErrorUnexpected(cons, "MTPcontactSuggested");
-
-	if (!data) setData(new MTPDcontactSuggested());
-	MTPDcontactSuggested &v(_contactSuggested());
-	v.vuser_id.read(from, end);
-	v.vmutual_contacts.read(from, end);
-}
-inline void MTPcontactSuggested::write(mtpBuffer &to) const {
-	const MTPDcontactSuggested &v(c_contactSuggested());
-	v.vuser_id.write(to);
-	v.vmutual_contacts.write(to);
-}
-inline MTPcontactSuggested::MTPcontactSuggested(MTPDcontactSuggested *_data) : mtpDataOwner(_data) {
-}
-inline MTPcontactSuggested MTP_contactSuggested(MTPint _user_id, MTPint _mutual_contacts) {
-	return MTPcontactSuggested(new MTPDcontactSuggested(_user_id, _mutual_contacts));
-}
-
 inline MTPcontactStatus::MTPcontactStatus() : mtpDataOwner(new MTPDcontactStatus()) {
 }
 
@@ -25442,35 +24513,6 @@ inline MTPcontacts_blocked MTP_contacts_blockedSlice(MTPint _count, const MTPVec
 	return MTPcontacts_blocked(new MTPDcontacts_blockedSlice(_count, _blocked, _users));
 }
 
-inline MTPcontacts_suggested::MTPcontacts_suggested() : mtpDataOwner(new MTPDcontacts_suggested()) {
-}
-
-inline uint32 MTPcontacts_suggested::innerLength() const {
-	const MTPDcontacts_suggested &v(c_contacts_suggested());
-	return v.vresults.innerLength() + v.vusers.innerLength();
-}
-inline mtpTypeId MTPcontacts_suggested::type() const {
-	return mtpc_contacts_suggested;
-}
-inline void MTPcontacts_suggested::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != mtpc_contacts_suggested) throw mtpErrorUnexpected(cons, "MTPcontacts_suggested");
-
-	if (!data) setData(new MTPDcontacts_suggested());
-	MTPDcontacts_suggested &v(_contacts_suggested());
-	v.vresults.read(from, end);
-	v.vusers.read(from, end);
-}
-inline void MTPcontacts_suggested::write(mtpBuffer &to) const {
-	const MTPDcontacts_suggested &v(c_contacts_suggested());
-	v.vresults.write(to);
-	v.vusers.write(to);
-}
-inline MTPcontacts_suggested::MTPcontacts_suggested(MTPDcontacts_suggested *_data) : mtpDataOwner(_data) {
-}
-inline MTPcontacts_suggested MTP_contacts_suggested(const MTPVector<MTPContactSuggested> &_results, const MTPVector<MTPUser> &_users) {
-	return MTPcontacts_suggested(new MTPDcontacts_suggested(_results, _users));
-}
-
 inline uint32 MTPmessages_dialogs::innerLength() const {
 	switch (_type) {
 		case mtpc_messages_dialogs: {
@@ -25756,10 +24798,10 @@ inline void MTPmessagesFilter::read(const mtpPrime *&from, const mtpPrime *end,
 		case mtpc_inputMessagesFilterPhotoVideo: _type = cons; break;
 		case mtpc_inputMessagesFilterPhotoVideoDocuments: _type = cons; break;
 		case mtpc_inputMessagesFilterDocument: _type = cons; break;
-		case mtpc_inputMessagesFilterAudio: _type = cons; break;
-		case mtpc_inputMessagesFilterAudioDocuments: _type = cons; break;
 		case mtpc_inputMessagesFilterUrl: _type = cons; break;
 		case mtpc_inputMessagesFilterGif: _type = cons; break;
+		case mtpc_inputMessagesFilterVoice: _type = cons; break;
+		case mtpc_inputMessagesFilterMusic: _type = cons; break;
 		default: throw mtpErrorUnexpected(cons, "MTPmessagesFilter");
 	}
 }
@@ -25775,10 +24817,10 @@ inline MTPmessagesFilter::MTPmessagesFilter(mtpTypeId type) : _type(type) {
 		case mtpc_inputMessagesFilterPhotoVideo: break;
 		case mtpc_inputMessagesFilterPhotoVideoDocuments: break;
 		case mtpc_inputMessagesFilterDocument: break;
-		case mtpc_inputMessagesFilterAudio: break;
-		case mtpc_inputMessagesFilterAudioDocuments: break;
 		case mtpc_inputMessagesFilterUrl: break;
 		case mtpc_inputMessagesFilterGif: break;
+		case mtpc_inputMessagesFilterVoice: break;
+		case mtpc_inputMessagesFilterMusic: break;
 		default: throw mtpErrorBadTypeId(type, "MTPmessagesFilter");
 	}
 }
@@ -25800,18 +24842,18 @@ inline MTPmessagesFilter MTP_inputMessagesFilterPhotoVideoDocuments() {
 inline MTPmessagesFilter MTP_inputMessagesFilterDocument() {
 	return MTPmessagesFilter(mtpc_inputMessagesFilterDocument);
 }
-inline MTPmessagesFilter MTP_inputMessagesFilterAudio() {
-	return MTPmessagesFilter(mtpc_inputMessagesFilterAudio);
-}
-inline MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments() {
-	return MTPmessagesFilter(mtpc_inputMessagesFilterAudioDocuments);
-}
 inline MTPmessagesFilter MTP_inputMessagesFilterUrl() {
 	return MTPmessagesFilter(mtpc_inputMessagesFilterUrl);
 }
 inline MTPmessagesFilter MTP_inputMessagesFilterGif() {
 	return MTPmessagesFilter(mtpc_inputMessagesFilterGif);
 }
+inline MTPmessagesFilter MTP_inputMessagesFilterVoice() {
+	return MTPmessagesFilter(mtpc_inputMessagesFilterVoice);
+}
+inline MTPmessagesFilter MTP_inputMessagesFilterMusic() {
+	return MTPmessagesFilter(mtpc_inputMessagesFilterMusic);
+}
 
 inline uint32 MTPupdate::innerLength() const {
 	switch (_type) {
@@ -25975,6 +25017,10 @@ inline uint32 MTPupdate::innerLength() const {
 			const MTPDupdateBotInlineQuery &v(c_updateBotInlineQuery());
 			return v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vquery.innerLength() + v.voffset.innerLength();
 		}
+		case mtpc_updateBotInlineSend: {
+			const MTPDupdateBotInlineSend &v(c_updateBotInlineSend());
+			return v.vuser_id.innerLength() + v.vquery.innerLength() + v.vid.innerLength();
+		}
 	}
 	return 0;
 }
@@ -26252,6 +25298,13 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI
 			v.vquery.read(from, end);
 			v.voffset.read(from, end);
 		} break;
+		case mtpc_updateBotInlineSend: _type = cons; {
+			if (!data) setData(new MTPDupdateBotInlineSend());
+			MTPDupdateBotInlineSend &v(_updateBotInlineSend());
+			v.vuser_id.read(from, end);
+			v.vquery.read(from, end);
+			v.vid.read(from, end);
+		} break;
 		default: throw mtpErrorUnexpected(cons, "MTPupdate");
 	}
 }
@@ -26482,6 +25535,12 @@ inline void MTPupdate::write(mtpBuffer &to) const {
 			v.vquery.write(to);
 			v.voffset.write(to);
 		} break;
+		case mtpc_updateBotInlineSend: {
+			const MTPDupdateBotInlineSend &v(c_updateBotInlineSend());
+			v.vuser_id.write(to);
+			v.vquery.write(to);
+			v.vid.write(to);
+		} break;
 	}
 }
 inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) {
@@ -26528,6 +25587,7 @@ inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) {
 		case mtpc_updateStickerSets: break;
 		case mtpc_updateSavedGifs: break;
 		case mtpc_updateBotInlineQuery: setData(new MTPDupdateBotInlineQuery()); break;
+		case mtpc_updateBotInlineSend: setData(new MTPDupdateBotInlineSend()); break;
 		default: throw mtpErrorBadTypeId(type, "MTPupdate");
 	}
 }
@@ -26611,6 +25671,8 @@ inline MTPupdate::MTPupdate(MTPDupdateStickerSetsOrder *_data) : mtpDataOwner(_d
 }
 inline MTPupdate::MTPupdate(MTPDupdateBotInlineQuery *_data) : mtpDataOwner(_data), _type(mtpc_updateBotInlineQuery) {
 }
+inline MTPupdate::MTPupdate(MTPDupdateBotInlineSend *_data) : mtpDataOwner(_data), _type(mtpc_updateBotInlineSend) {
+}
 inline MTPupdate MTP_updateNewMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count) {
 	return MTPupdate(new MTPDupdateNewMessage(_message, _pts, _pts_count));
 }
@@ -26737,6 +25799,9 @@ inline MTPupdate MTP_updateSavedGifs() {
 inline MTPupdate MTP_updateBotInlineQuery(const MTPlong &_query_id, MTPint _user_id, const MTPstring &_query, const MTPstring &_offset) {
 	return MTPupdate(new MTPDupdateBotInlineQuery(_query_id, _user_id, _query, _offset));
 }
+inline MTPupdate MTP_updateBotInlineSend(MTPint _user_id, const MTPstring &_query, const MTPstring &_id) {
+	return MTPupdate(new MTPDupdateBotInlineSend(_user_id, _query, _id));
+}
 
 inline MTPupdates_state::MTPupdates_state() : mtpDataOwner(new MTPDupdates_state()) {
 }
@@ -27983,57 +27048,6 @@ inline MTPmessages_sentEncryptedMessage MTP_messages_sentEncryptedFile(MTPint _d
 	return MTPmessages_sentEncryptedMessage(new MTPDmessages_sentEncryptedFile(_date, _file));
 }
 
-inline uint32 MTPinputAudio::innerLength() const {
-	switch (_type) {
-		case mtpc_inputAudio: {
-			const MTPDinputAudio &v(c_inputAudio());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength();
-		}
-	}
-	return 0;
-}
-inline mtpTypeId MTPinputAudio::type() const {
-	if (!_type) throw mtpErrorUninitialized();
-	return _type;
-}
-inline void MTPinputAudio::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != _type) setData(0);
-	switch (cons) {
-		case mtpc_inputAudioEmpty: _type = cons; break;
-		case mtpc_inputAudio: _type = cons; {
-			if (!data) setData(new MTPDinputAudio());
-			MTPDinputAudio &v(_inputAudio());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-		} break;
-		default: throw mtpErrorUnexpected(cons, "MTPinputAudio");
-	}
-}
-inline void MTPinputAudio::write(mtpBuffer &to) const {
-	switch (_type) {
-		case mtpc_inputAudio: {
-			const MTPDinputAudio &v(c_inputAudio());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-		} break;
-	}
-}
-inline MTPinputAudio::MTPinputAudio(mtpTypeId type) : mtpDataOwner(0), _type(type) {
-	switch (type) {
-		case mtpc_inputAudioEmpty: break;
-		case mtpc_inputAudio: setData(new MTPDinputAudio()); break;
-		default: throw mtpErrorBadTypeId(type, "MTPinputAudio");
-	}
-}
-inline MTPinputAudio::MTPinputAudio(MTPDinputAudio *_data) : mtpDataOwner(_data), _type(mtpc_inputAudio) {
-}
-inline MTPinputAudio MTP_inputAudioEmpty() {
-	return MTPinputAudio(mtpc_inputAudioEmpty);
-}
-inline MTPinputAudio MTP_inputAudio(const MTPlong &_id, const MTPlong &_access_hash) {
-	return MTPinputAudio(new MTPDinputAudio(_id, _access_hash));
-}
-
 inline uint32 MTPinputDocument::innerLength() const {
 	switch (_type) {
 		case mtpc_inputDocument: {
@@ -28085,81 +27099,6 @@ inline MTPinputDocument MTP_inputDocument(const MTPlong &_id, const MTPlong &_ac
 	return MTPinputDocument(new MTPDinputDocument(_id, _access_hash));
 }
 
-inline uint32 MTPaudio::innerLength() const {
-	switch (_type) {
-		case mtpc_audioEmpty: {
-			const MTPDaudioEmpty &v(c_audioEmpty());
-			return v.vid.innerLength();
-		}
-		case mtpc_audio: {
-			const MTPDaudio &v(c_audio());
-			return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vduration.innerLength() + v.vmime_type.innerLength() + v.vsize.innerLength() + v.vdc_id.innerLength();
-		}
-	}
-	return 0;
-}
-inline mtpTypeId MTPaudio::type() const {
-	if (!_type) throw mtpErrorUninitialized();
-	return _type;
-}
-inline void MTPaudio::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
-	if (cons != _type) setData(0);
-	switch (cons) {
-		case mtpc_audioEmpty: _type = cons; {
-			if (!data) setData(new MTPDaudioEmpty());
-			MTPDaudioEmpty &v(_audioEmpty());
-			v.vid.read(from, end);
-		} break;
-		case mtpc_audio: _type = cons; {
-			if (!data) setData(new MTPDaudio());
-			MTPDaudio &v(_audio());
-			v.vid.read(from, end);
-			v.vaccess_hash.read(from, end);
-			v.vdate.read(from, end);
-			v.vduration.read(from, end);
-			v.vmime_type.read(from, end);
-			v.vsize.read(from, end);
-			v.vdc_id.read(from, end);
-		} break;
-		default: throw mtpErrorUnexpected(cons, "MTPaudio");
-	}
-}
-inline void MTPaudio::write(mtpBuffer &to) const {
-	switch (_type) {
-		case mtpc_audioEmpty: {
-			const MTPDaudioEmpty &v(c_audioEmpty());
-			v.vid.write(to);
-		} break;
-		case mtpc_audio: {
-			const MTPDaudio &v(c_audio());
-			v.vid.write(to);
-			v.vaccess_hash.write(to);
-			v.vdate.write(to);
-			v.vduration.write(to);
-			v.vmime_type.write(to);
-			v.vsize.write(to);
-			v.vdc_id.write(to);
-		} break;
-	}
-}
-inline MTPaudio::MTPaudio(mtpTypeId type) : mtpDataOwner(0), _type(type) {
-	switch (type) {
-		case mtpc_audioEmpty: setData(new MTPDaudioEmpty()); break;
-		case mtpc_audio: setData(new MTPDaudio()); break;
-		default: throw mtpErrorBadTypeId(type, "MTPaudio");
-	}
-}
-inline MTPaudio::MTPaudio(MTPDaudioEmpty *_data) : mtpDataOwner(_data), _type(mtpc_audioEmpty) {
-}
-inline MTPaudio::MTPaudio(MTPDaudio *_data) : mtpDataOwner(_data), _type(mtpc_audio) {
-}
-inline MTPaudio MTP_audioEmpty(const MTPlong &_id) {
-	return MTPaudio(new MTPDaudioEmpty(_id));
-}
-inline MTPaudio MTP_audio(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, MTPint _duration, const MTPstring &_mime_type, MTPint _size, MTPint _dc_id) {
-	return MTPaudio(new MTPDaudio(_id, _access_hash, _date, _duration, _mime_type, _size, _dc_id));
-}
-
 inline uint32 MTPdocument::innerLength() const {
 	switch (_type) {
 		case mtpc_documentEmpty: {
@@ -28491,28 +27430,64 @@ inline uint32 MTPinputPrivacyKey::innerLength() const {
 	return 0;
 }
 inline mtpTypeId MTPinputPrivacyKey::type() const {
-	return mtpc_inputPrivacyKeyStatusTimestamp;
+	if (!_type) throw mtpErrorUninitialized();
+	return _type;
 }
 inline void MTPinputPrivacyKey::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
+	switch (cons) {
+		case mtpc_inputPrivacyKeyStatusTimestamp: _type = cons; break;
+		case mtpc_inputPrivacyKeyChatInvite: _type = cons; break;
+		default: throw mtpErrorUnexpected(cons, "MTPinputPrivacyKey");
+	}
 }
 inline void MTPinputPrivacyKey::write(mtpBuffer &to) const {
+	switch (_type) {
+	}
+}
+inline MTPinputPrivacyKey::MTPinputPrivacyKey(mtpTypeId type) : _type(type) {
+	switch (type) {
+		case mtpc_inputPrivacyKeyStatusTimestamp: break;
+		case mtpc_inputPrivacyKeyChatInvite: break;
+		default: throw mtpErrorBadTypeId(type, "MTPinputPrivacyKey");
+	}
 }
 inline MTPinputPrivacyKey MTP_inputPrivacyKeyStatusTimestamp() {
-	return MTPinputPrivacyKey();
+	return MTPinputPrivacyKey(mtpc_inputPrivacyKeyStatusTimestamp);
+}
+inline MTPinputPrivacyKey MTP_inputPrivacyKeyChatInvite() {
+	return MTPinputPrivacyKey(mtpc_inputPrivacyKeyChatInvite);
 }
 
 inline uint32 MTPprivacyKey::innerLength() const {
 	return 0;
 }
 inline mtpTypeId MTPprivacyKey::type() const {
-	return mtpc_privacyKeyStatusTimestamp;
+	if (!_type) throw mtpErrorUninitialized();
+	return _type;
 }
 inline void MTPprivacyKey::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
+	switch (cons) {
+		case mtpc_privacyKeyStatusTimestamp: _type = cons; break;
+		case mtpc_privacyKeyChatInvite: _type = cons; break;
+		default: throw mtpErrorUnexpected(cons, "MTPprivacyKey");
+	}
 }
 inline void MTPprivacyKey::write(mtpBuffer &to) const {
+	switch (_type) {
+	}
+}
+inline MTPprivacyKey::MTPprivacyKey(mtpTypeId type) : _type(type) {
+	switch (type) {
+		case mtpc_privacyKeyStatusTimestamp: break;
+		case mtpc_privacyKeyChatInvite: break;
+		default: throw mtpErrorBadTypeId(type, "MTPprivacyKey");
+	}
 }
 inline MTPprivacyKey MTP_privacyKeyStatusTimestamp() {
-	return MTPprivacyKey();
+	return MTPprivacyKey(mtpc_privacyKeyStatusTimestamp);
+}
+inline MTPprivacyKey MTP_privacyKeyChatInvite() {
+	return MTPprivacyKey(mtpc_privacyKeyChatInvite);
 }
 
 inline uint32 MTPinputPrivacyRule::innerLength() const {
@@ -28782,7 +27757,7 @@ inline uint32 MTPdocumentAttribute::innerLength() const {
 		}
 		case mtpc_documentAttributeAudio: {
 			const MTPDdocumentAttributeAudio &v(c_documentAttributeAudio());
-			return v.vduration.innerLength() + v.vtitle.innerLength() + v.vperformer.innerLength();
+			return v.vflags.innerLength() + v.vduration.innerLength() + (v.has_title() ? v.vtitle.innerLength() : 0) + (v.has_performer() ? v.vperformer.innerLength() : 0) + (v.has_waveform() ? v.vwaveform.innerLength() : 0);
 		}
 		case mtpc_documentAttributeFilename: {
 			const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename());
@@ -28821,9 +27796,11 @@ inline void MTPdocumentAttribute::read(const mtpPrime *&from, const mtpPrime *en
 		case mtpc_documentAttributeAudio: _type = cons; {
 			if (!data) setData(new MTPDdocumentAttributeAudio());
 			MTPDdocumentAttributeAudio &v(_documentAttributeAudio());
+			v.vflags.read(from, end);
 			v.vduration.read(from, end);
-			v.vtitle.read(from, end);
-			v.vperformer.read(from, end);
+			if (v.has_title()) { v.vtitle.read(from, end); } else { v.vtitle = MTPstring(); }
+			if (v.has_performer()) { v.vperformer.read(from, end); } else { v.vperformer = MTPstring(); }
+			if (v.has_waveform()) { v.vwaveform.read(from, end); } else { v.vwaveform = MTPbytes(); }
 		} break;
 		case mtpc_documentAttributeFilename: _type = cons; {
 			if (!data) setData(new MTPDdocumentAttributeFilename());
@@ -28853,9 +27830,11 @@ inline void MTPdocumentAttribute::write(mtpBuffer &to) const {
 		} break;
 		case mtpc_documentAttributeAudio: {
 			const MTPDdocumentAttributeAudio &v(c_documentAttributeAudio());
+			v.vflags.write(to);
 			v.vduration.write(to);
-			v.vtitle.write(to);
-			v.vperformer.write(to);
+			if (v.has_title()) v.vtitle.write(to);
+			if (v.has_performer()) v.vperformer.write(to);
+			if (v.has_waveform()) v.vwaveform.write(to);
 		} break;
 		case mtpc_documentAttributeFilename: {
 			const MTPDdocumentAttributeFilename &v(c_documentAttributeFilename());
@@ -28896,8 +27875,8 @@ inline MTPdocumentAttribute MTP_documentAttributeSticker(const MTPstring &_alt,
 inline MTPdocumentAttribute MTP_documentAttributeVideo(MTPint _duration, MTPint _w, MTPint _h) {
 	return MTPdocumentAttribute(new MTPDdocumentAttributeVideo(_duration, _w, _h));
 }
-inline MTPdocumentAttribute MTP_documentAttributeAudio(MTPint _duration, const MTPstring &_title, const MTPstring &_performer) {
-	return MTPdocumentAttribute(new MTPDdocumentAttributeAudio(_duration, _title, _performer));
+inline MTPdocumentAttribute MTP_documentAttributeAudio(MTPint _flags, MTPint _duration, const MTPstring &_title, const MTPstring &_performer, const MTPbytes &_waveform) {
+	return MTPdocumentAttribute(new MTPDdocumentAttributeAudio(_flags, _duration, _title, _performer, _waveform));
 }
 inline MTPdocumentAttribute MTP_documentAttributeFilename(const MTPstring &_file_name) {
 	return MTPdocumentAttribute(new MTPDdocumentAttributeFilename(_file_name));
diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl
index 64bfa2eaa..499823df8 100644
--- a/Telegram/SourceFiles/mtproto/scheme.tl
+++ b/Telegram/SourceFiles/mtproto/scheme.tl
@@ -150,11 +150,6 @@ inputMediaUploadedPhoto#f7aff1c0 file:InputFile caption:string = InputMedia;
 inputMediaPhoto#e9bfb4f3 id:InputPhoto caption:string = InputMedia;
 inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
 inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia;
-inputMediaUploadedVideo#82713fdf file:InputFile duration:int w:int h:int mime_type:string caption:string = InputMedia;
-inputMediaUploadedThumbVideo#7780ddf9 file:InputFile thumb:InputFile duration:int w:int h:int mime_type:string caption:string = InputMedia;
-inputMediaVideo#936a4ebd id:InputVideo caption:string = InputMedia;
-inputMediaUploadedAudio#4e498cab file:InputFile duration:int mime_type:string = InputMedia;
-inputMediaAudio#89938781 id:InputAudio = InputMedia;
 inputMediaUploadedDocument#1d89306d file:InputFile mime_type:string attributes:Vector<DocumentAttribute> caption:string = InputMedia;
 inputMediaUploadedThumbDocument#ad613491 file:InputFile thumb:InputFile mime_type:string attributes:Vector<DocumentAttribute> caption:string = InputMedia;
 inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia;
@@ -171,13 +166,8 @@ inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
 inputPhotoEmpty#1cd7bf0d = InputPhoto;
 inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
 
-inputVideoEmpty#5508ec75 = InputVideo;
-inputVideo#ee579652 id:long access_hash:long = InputVideo;
-
 inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
-inputVideoFileLocation#3d0364ec id:long access_hash:long = InputFileLocation;
 inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
-inputAudioFileLocation#74dc404d id:long access_hash:long = InputFileLocation;
 inputDocumentFileLocation#4e45abe9 id:long access_hash:long = InputFileLocation;
 
 inputPhotoCropAuto#ade6b004 = InputPhotoCrop;
@@ -219,7 +209,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
 chatEmpty#9ba2d800 id:int = Chat;
 chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat;
 chatForbidden#7328bdb id:int title:string = Chat;
-channel#4b1b7506 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true editor:flags.3?true moderator:flags.4?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true id:int access_hash:long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string = Chat;
+channel#4b1b7506 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true editor:flags.3?true moderator:flags.4?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true invites_enabled:flags.10?true id:int access_hash:long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string = Chat;
 channelForbidden#2d85832c id:int access_hash:long title:string = Chat;
 
 chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
@@ -241,12 +231,10 @@ messageService#c06b9607 flags:# unread:flags.0?true out:flags.1?true mentioned:f
 
 messageMediaEmpty#3ded6320 = MessageMedia;
 messageMediaPhoto#3d8ce53d photo:Photo caption:string = MessageMedia;
-messageMediaVideo#5bcf1675 video:Video caption:string = MessageMedia;
 messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
 messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia;
 messageMediaUnsupported#9f84f49e = MessageMedia;
 messageMediaDocument#f3e02ea8 document:Document caption:string = MessageMedia;
-messageMediaAudio#c6b68300 audio:Audio = MessageMedia;
 messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
 messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia;
 
@@ -272,9 +260,6 @@ photoSizeEmpty#e17e23c type:string = PhotoSize;
 photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
 photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
 
-videoEmpty#c10658a8 id:long = Video;
-video#f72887d3 id:long access_hash:long date:int duration:int mime_type:string size:int thumb:PhotoSize dc_id:int w:int h:int = Video;
-
 geoPointEmpty#1117dd5f = GeoPoint;
 geoPoint#2049d70c long:double lat:double = GeoPoint;
 
@@ -319,8 +304,6 @@ importedContact#d0028438 user_id:int client_id:long = ImportedContact;
 
 contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
 
-contactSuggested#3de191a1 user_id:int mutual_contacts:int = ContactSuggested;
-
 contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
 
 contacts.link#3ace484c my_link:ContactLink foreign_link:ContactLink user:User = contacts.Link;
@@ -333,8 +316,6 @@ contacts.importedContacts#ad524315 imported:Vector<ImportedContact> retry_contac
 contacts.blocked#1c138d15 blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
 contacts.blockedSlice#900802a1 count:int blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
 
-contacts.suggested#5649dcc5 results:Vector<ContactSuggested> users:Vector<User> = contacts.Suggested;
-
 messages.dialogs#15ba6c40 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
 messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
 
@@ -354,10 +335,10 @@ inputMessagesFilterVideo#9fc00e65 = MessagesFilter;
 inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
 inputMessagesFilterPhotoVideoDocuments#d95e73bb = MessagesFilter;
 inputMessagesFilterDocument#9eddf188 = MessagesFilter;
-inputMessagesFilterAudio#cfc87522 = MessagesFilter;
-inputMessagesFilterAudioDocuments#5afbf764 = MessagesFilter;
 inputMessagesFilterUrl#7ef0dd87 = MessagesFilter;
 inputMessagesFilterGif#ffc86587 = MessagesFilter;
+inputMessagesFilterVoice#50f5c392 = MessagesFilter;
+inputMessagesFilterMusic#3751b49e = MessagesFilter;
 
 updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
 updateMessageID#4e90bfd6 id:int random_id:long = Update;
@@ -401,6 +382,7 @@ updateStickerSetsOrder#f0dfb451 order:Vector<long> = Update;
 updateStickerSets#43ae3dec = Update;
 updateSavedGifs#9375341e = Update;
 updateBotInlineQuery#c01eea08 query_id:long user_id:int query:string offset:string = Update;
+updateBotInlineSend#f69e113 user_id:int query:string id:string = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -459,15 +441,9 @@ messages.dhConfig#2c221edd g:int p:bytes version:int random:bytes = messages.DhC
 messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage;
 messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage;
 
-inputAudioEmpty#d95adc84 = InputAudio;
-inputAudio#77d440ff id:long access_hash:long = InputAudio;
-
 inputDocumentEmpty#72f0eaae = InputDocument;
 inputDocument#18798952 id:long access_hash:long = InputDocument;
 
-audioEmpty#586988d8 id:long = Audio;
-audio#f9e35055 id:long access_hash:long date:int duration:int mime_type:string size:int dc_id:int = Audio;
-
 documentEmpty#36f8c871 id:long = Document;
 document#f9a39f4f id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = Document;
 
@@ -492,8 +468,10 @@ sendMessageChooseContactAction#628cbc6f = SendMessageAction;
 contacts.found#1aa1f784 results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
 
 inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
+inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey;
 
 privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
+privacyKeyChatInvite#500e6dfa = PrivacyKey;
 
 inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
 inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
@@ -519,7 +497,7 @@ documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
 documentAttributeAnimated#11b58939 = DocumentAttribute;
 documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute;
 documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute;
-documentAttributeAudio#ded218e0 duration:int title:string performer:string = DocumentAttribute;
+documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
 documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
 
 messages.stickersNotModified#f1749a22 = messages.Stickers;
@@ -552,7 +530,7 @@ account.password#7c18141c current_salt:bytes new_salt:bytes hint:string has_reco
 
 account.passwordSettings#b7b72ab3 email:string = account.PasswordSettings;
 
-account.passwordInputSettings#bcfc532c flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings;
+account.passwordInputSettings#86916deb flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings;
 
 auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
 
@@ -717,7 +695,6 @@ users.getFullUser#ca30a5b1 id:InputUser = UserFull;
 contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
 contacts.getContacts#22c6aa08 hash:string = contacts.Contacts;
 contacts.importContacts#da30b32d contacts:Vector<InputContact> replace:Bool = contacts.ImportedContacts;
-contacts.getSuggested#cd773428 limit:int = contacts.Suggested;
 contacts.deleteContact#8e953744 id:InputUser = contacts.Link;
 contacts.deleteContacts#59ab389e id:Vector<InputUser> = Bool;
 contacts.block#332b49fc id:InputUser = Bool;
@@ -730,7 +707,7 @@ contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
 
 messages.getMessages#4222fa74 id:Vector<int> = messages.Messages;
 messages.getDialogs#6b47f94d offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
-messages.getHistory#8a8ec2da peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.getHistory#afa92846 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 messages.search#d4569248 flags:# important_only:flags.0?true peer:InputPeer q:string filter:MessagesFilter min_date:int max_date:int offset:int max_id:int limit:int = messages.Messages;
 messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
 messages.deleteHistory#b7c13bd9 peer:InputPeer max_id:int = messages.AffectedHistory;
@@ -808,7 +785,7 @@ help.getAppChangelog#5bab7fb2 device_model:string system_version:string app_vers
 help.getTermsOfService#37d78f83 lang_code:string = help.TermsOfService;
 
 channels.getDialogs#a9d3d249 offset:int limit:int = messages.Dialogs;
-channels.getImportantHistory#ddb929cb channel:InputChannel offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+channels.getImportantHistory#8f494bb2 channel:InputChannel offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
 channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
 channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
@@ -832,3 +809,4 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> =
 channels.kickFromChannel#a672de14 channel:InputChannel user_id:InputUser kicked:Bool = Updates;
 channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite;
 channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
+channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates;
diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp
index 797c76bea..dd9cf6dbe 100644
--- a/Telegram/SourceFiles/overviewwidget.cpp
+++ b/Telegram/SourceFiles/overviewwidget.cpp
@@ -39,7 +39,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD
 , _resizeSkip(0)
 , _peer(peer->migrateTo() ? peer->migrateTo() : peer)
 , _type(type)
-, _reversed(_type != OverviewDocuments && _type != OverviewLinks)
+, _reversed(_type != OverviewFiles && _type != OverviewLinks)
 , _migrated(_peer->migrateFrom() ? App::history(_peer->migrateFrom()->id) : 0)
 , _history(App::history(_peer->id))
 , _channel(peerToChannel(_peer->id))
@@ -108,7 +108,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerD
 	connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchMessages()));
 
 	_cancelSearch.hide();
-	if (_type == OverviewLinks || _type == OverviewDocuments) {
+	if (_type == OverviewLinks || _type == OverviewFiles) {
 		_search.show();
 	} else {
 		_search.hide();
@@ -736,7 +736,7 @@ QPoint OverviewInner::mapMouseToItem(QPoint p, MsgId itemId, int32 itemIndex) {
 }
 
 void OverviewInner::activate() {
-	if (_type == OverviewLinks || _type == OverviewDocuments) {
+	if (_type == OverviewLinks || _type == OverviewFiles) {
 		_search.setFocus();
 	} else {
 		setFocus();
@@ -760,7 +760,7 @@ void OverviewInner::clear() {
 }
 
 int32 OverviewInner::itemTop(const FullMsgId &msgId) const {
-	if (_type == OverviewAudioDocuments) {
+	if (_type == OverviewMusicFiles) {
 		int32 itemIndex = -1;
 		fixItemIndex(itemIndex, (msgId.channel == _channel) ? msgId.msg : ((_migrated && msgId.channel == _migrated->channelId()) ? -msgId.msg : 0));
 		if (itemIndex >= 0) {
@@ -1261,10 +1261,10 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 
 	_contextMenuLnk = textlnkOver();
 	PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
+	bool lnkIsAudio = lnkDocument ? lnkDocument->document()->voice() : false;
+	bool lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideo() : false;
+	if (lnkPhoto || lnkDocument) {
 		_menu = new PopupMenu();
 		if (App::hoveredLinkItem()) {
 			_menu->addAction(lang(lng_context_to_msg), this, SLOT(goToMessage()))->setEnabled(true);
@@ -1272,14 +1272,14 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
 		if (lnkPhoto) {
 			_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
 		} else {
-			if ((lnkVideo && lnkVideo->video()->loading()) || (lnkAudio && lnkAudio->audio()->loading()) || (lnkDocument && lnkDocument->document()->loading())) {
+			if (lnkDocument && lnkDocument->document()->loading()) {
 				_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
 			} else {
-				if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
+				if (lnkDocument && !lnkDocument->document()->already(true).isEmpty()) {
 					_menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
 				}
-				_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_file)), this, SLOT(openContextFile()))->setEnabled(true);
-				_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_file)), this, SLOT(saveContextFile()))->setEnabled(true);
+				_menu->addAction(lang(lnkIsVideo ? lng_context_open_video : (lnkIsAudio ? lng_context_open_audio : lng_context_open_file)), this, SLOT(openContextFile()))->setEnabled(true);
+				_menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsAudio ? lng_context_save_audio : lng_context_save_file)), this, SLOT(saveContextFile()))->setEnabled(true);
 			}
 		}
 		if (isUponSelected > 1) {
@@ -1421,8 +1421,8 @@ void OverviewInner::switchType(MediaOverviewType type) {
 	if (_type != type) {
 		clear();
 		_type = type;
-		_reversed = (_type != OverviewLinks && _type != OverviewDocuments);
-		if (_type == OverviewLinks || _type == OverviewDocuments) {
+		_reversed = (_type != OverviewLinks && _type != OverviewFiles);
+		if (_type == OverviewLinks || _type == OverviewFiles) {
 			_search.show();
 		} else {
 			_search.hide();
@@ -1502,43 +1502,27 @@ void OverviewInner::selectMessage() {
 }
 
 void OverviewInner::cancelContextDownload() {
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) {
-		lnkVideo->video()->cancel();
-	} else if (lnkAudio) {
-		lnkAudio->audio()->cancel();
-	} else if (lnkDocument) {
+	if (lnkDocument) {
 		lnkDocument->document()->cancel();
 	}
 }
 
 void OverviewInner::showContextInFolder() {
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	QString already = lnkVideo ? lnkVideo->video()->already(true) : (lnkAudio ? lnkAudio->audio()->already(true) : (lnkDocument ? lnkDocument->document()->already(true) : QString()));
+	QString already = lnkDocument ? lnkDocument->document()->already(true) : QString();
 	if (!already.isEmpty()) psShowInFolder(already);
 }
 
 void OverviewInner::saveContextFile() {
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) VideoSaveLink::doSave(lnkVideo->video(), true);
-	if (lnkAudio) AudioSaveLink::doSave(lnkAudio->audio(), true);
 	if (lnkDocument) DocumentSaveLink::doSave(lnkDocument->document(), true);
 }
 
 void OverviewInner::openContextFile() {
 	HistoryItem *was = App::hoveredLinkItem();
 	App::hoveredLinkItem(App::contextItem());
-	VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
-	AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
 	DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
-	if (lnkVideo) VideoOpenLink(lnkVideo->video()).onClick(Qt::LeftButton);
-	if (lnkAudio) AudioOpenLink(lnkAudio->audio()).onClick(Qt::LeftButton);
 	if (lnkDocument) DocumentOpenLink(lnkDocument->document()).onClick(Qt::LeftButton);
 	App::hoveredLinkItem(was);
 }
@@ -1582,7 +1566,7 @@ void OverviewInner::onNeedSearchMessages() {
 }
 
 void OverviewInner::onSearchUpdate() {
-	QString filterText = (_type == OverviewLinks || _type == OverviewDocuments) ? _search.text().trimmed() : QString();
+	QString filterText = (_type == OverviewLinks || _type == OverviewFiles) ? _search.text().trimmed() : QString();
 	bool inSearch = !filterText.isEmpty(), changed = (inSearch != _inSearch);
 	_inSearch = inSearch;
 
@@ -1730,7 +1714,7 @@ void OverviewInner::mediaOverviewUpdated() {
 
 		_height = countHeight();
 	} else {
-		bool dateEveryMonth = (_type == OverviewDocuments), dateEveryDay = (_type == OverviewLinks);
+		bool dateEveryMonth = (_type == OverviewFiles), dateEveryDay = (_type == OverviewLinks);
 		bool withDates = (dateEveryMonth || dateEveryDay);
 
 		History::MediaOverview &o(_history->overview[_type]), *migratedOverview = _migrated ? &_migrated->overview[_type] : 0;
@@ -1794,7 +1778,7 @@ void OverviewInner::mediaOverviewUpdated() {
 	int32 newHeight = _marginTop + _height + _marginBottom, deltaHeight = newHeight - height();
 	if (deltaHeight) {
 		resize(_width, newHeight);
-		if (_type != OverviewLinks && _type != OverviewDocuments) {
+		if (_type != OverviewLinks && _type != OverviewFiles) {
 			_overview->scrollBy(deltaHeight);
 		}
 	} else {
@@ -1910,10 +1894,10 @@ void OverviewInner::recountMargins() {
 	if (_type == OverviewPhotos || _type == OverviewVideos) {
 		_marginBottom = 0;
 		_marginTop = qMax(_minHeight - _height - _marginBottom, 0);
-	} else if (_type == OverviewAudioDocuments) {
+	} else if (_type == OverviewMusicFiles) {
 		_marginTop = st::playlistPadding;
 		_marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding));
-	} else if (_type == OverviewLinks || _type == OverviewDocuments) {
+	} else if (_type == OverviewLinks || _type == OverviewFiles) {
 		_marginTop = st::linksSearchMargin.top() + _search.height() + st::linksSearchMargin.bottom();
 		_marginBottom = qMax(_minHeight - _height - _marginTop, int32(st::playlistPadding));
 	} else {
@@ -1937,19 +1921,19 @@ LayoutMediaItem *OverviewInner::layoutPrepare(HistoryItem *item) {
 	} else if (_type == OverviewVideos) {
 		if (media && media->type() == MediaTypeVideo) {
 			if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) {
-				i = _layoutItems.insert(item, new LayoutOverviewVideo(static_cast<HistoryVideo*>(media)->video(), item));
+				i = _layoutItems.insert(item, new LayoutOverviewVideo(media->getDocument(), item));
 				i.value()->initDimensions();
 			}
 		}
-	} else if (_type == OverviewAudios) {
-		if (media && media->type() == MediaTypeAudio) {
+	} else if (_type == OverviewVoiceFiles) {
+		if (media && (media->type() == MediaTypeVoiceFile)) {
 			if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) {
-				i = _layoutItems.insert(item, new LayoutOverviewAudio(static_cast<HistoryAudio*>(media)->audio(), item));
+				i = _layoutItems.insert(item, new LayoutOverviewVoice(media->getDocument(), item));
 				i.value()->initDimensions();
 			}
 		}
-	} else if (_type == OverviewDocuments || _type == OverviewAudioDocuments) {
-		if (media && (media->type() == MediaTypeDocument || media->type() == MediaTypeGif)) {
+	} else if (_type == OverviewFiles || _type == OverviewMusicFiles) {
+		if (media && (media->type() == MediaTypeFile || media->type() == MediaTypeMusicFile || media->type() == MediaTypeGif)) {
 			if ((i = _layoutItems.constFind(item)) == _layoutItems.cend()) {
 				i = _layoutItems.insert(item, new LayoutOverviewDocument(media->getDocument(), item));
 				i.value()->initDimensions();
@@ -2033,7 +2017,7 @@ void OverviewWidget::onScroll() {
 	int32 preloadThreshold = _scroll.height() * 5;
 	bool needToPreload = false;
 	do {
-		needToPreload = (type() == OverviewLinks || type() == OverviewDocuments) ? (_scroll.scrollTop() + preloadThreshold > _scroll.scrollTopMax()) : (_scroll.scrollTop() < preloadThreshold);
+		needToPreload = (type() == OverviewLinks || type() == OverviewFiles) ? (_scroll.scrollTop() + preloadThreshold > _scroll.scrollTopMax()) : (_scroll.scrollTop() < preloadThreshold);
 		if (!needToPreload || !_inner.preloadLocal()) {
 			break;
 		}
@@ -2098,7 +2082,7 @@ void OverviewWidget::scrollBy(int32 add) {
 }
 
 void OverviewWidget::scrollReset() {
-	_scroll.scrollToY((type() == OverviewLinks || type() == OverviewDocuments) ? 0 : _scroll.scrollTopMax());
+	_scroll.scrollToY((type() == OverviewLinks || type() == OverviewFiles) ? 0 : _scroll.scrollTopMax());
 }
 
 void OverviewWidget::paintTopBar(QPainter &p, float64 over, int32 decreaseWidth) {
@@ -2143,9 +2127,9 @@ void OverviewWidget::switchType(MediaOverviewType type) {
 	switch (type) {
 	case OverviewPhotos: _header = lang(lng_profile_photos_header); break;
 	case OverviewVideos: _header = lang(lng_profile_videos_header); break;
-	case OverviewAudioDocuments: _header = lang(lng_profile_songs_header); break;
-	case OverviewDocuments: _header = lang(lng_profile_files_header); break;
-	case OverviewAudios: _header = lang(lng_profile_audios_header); break;
+	case OverviewMusicFiles: _header = lang(lng_profile_songs_header); break;
+	case OverviewFiles: _header = lang(lng_profile_files_header); break;
+	case OverviewVoiceFiles: _header = lang(lng_profile_audios_header); break;
 	case OverviewLinks: _header = lang(lng_profile_shared_links_header); break;
 	}
 	noSelectingScroll();
@@ -2184,7 +2168,7 @@ int32 OverviewWidget::lastScrollTop() const {
 }
 
 int32 OverviewWidget::countBestScroll() const {
-	if (type() == OverviewAudioDocuments && audioPlayer()) {
+	if (type() == OverviewMusicFiles && audioPlayer()) {
 		SongMsgId playing;
 		AudioPlayerState playingState = AudioPlayerStopped;
 		audioPlayer()->currentState(&playing, &playingState);
@@ -2194,7 +2178,7 @@ int32 OverviewWidget::countBestScroll() const {
 				return snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax());
 			}
 		}
-	} else if (type() == OverviewLinks || type() == OverviewDocuments) {
+	} else if (type() == OverviewLinks || type() == OverviewFiles) {
 		return 0;
 	}
 	return _scroll.scrollTopMax();
@@ -2351,7 +2335,7 @@ void OverviewWidget::onScrollTimer() {
 }
 
 void OverviewWidget::onPlayerSongChanged(const FullMsgId &msgId) {
-	if (type() == OverviewAudioDocuments) {
+	if (type() == OverviewMusicFiles) {
 //		int32 top = _inner.itemTop(msgId);
 //		if (top > 0) {
 //			_scroll.scrollToY(snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax()));
diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp
index 7fdaa1b6d..1ab72f6c5 100644
--- a/Telegram/SourceFiles/playerwidget.cpp
+++ b/Telegram/SourceFiles/playerwidget.cpp
@@ -198,7 +198,7 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) {
 			audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
 			if (playing == _song && playingDuration) {
 				if (playingState == AudioPlayerPlaying || playingState == AudioPlayerStarting || playingState == AudioPlayerResuming) {
-					audioPlayer()->pauseresume(OverviewDocuments);
+					audioPlayer()->pauseresume(OverviewFiles);
 				}
 				_down = OverPlayback;
 				_downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.);
@@ -210,7 +210,7 @@ void PlayerWidget::mousePressEvent(QMouseEvent *e) {
 			}
 		} else if (_over == OverFull && _song) {
 			if (HistoryItem *item = App::histItemById(_song.msgId)) {
-				App::main()->showMediaOverview(item->history()->peer, OverviewAudioDocuments);
+				App::main()->showMediaOverview(item->history()->peer, OverviewMusicFiles);
 			}
 		} else if (_over == OverRepeat) {
 			_repeat = !_repeat;
@@ -269,23 +269,23 @@ void PlayerWidget::updateControls() {
 	_fullAvailable = (_index >= 0);
 
 	History *history = _msgmigrated ? _migrated : _history;
-	_prevAvailable = _fullAvailable && ((_index > 0) || (_index == 0 && _migrated && !_msgmigrated && !_migrated->overview[OverviewAudioDocuments].isEmpty()));
-	_nextAvailable = _fullAvailable && ((_index < history->overview[OverviewAudioDocuments].size() - 1) || (_msgmigrated && _index == _migrated->overview[OverviewAudioDocuments].size() - 1 && _history->overviewLoaded(OverviewAudioDocuments) && _history->overviewCount(OverviewAudioDocuments) > 0));
+	_prevAvailable = _fullAvailable && ((_index > 0) || (_index == 0 && _migrated && !_msgmigrated && !_migrated->overview[OverviewMusicFiles].isEmpty()));
+	_nextAvailable = _fullAvailable && ((_index < history->overview[OverviewMusicFiles].size() - 1) || (_msgmigrated && _index == _migrated->overview[OverviewMusicFiles].size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0));
 	resizeEvent(0);
 	update();
 	if (_index >= 0 && _index < MediaOverviewStartPerPage) {
-		if (!_history->overviewLoaded(OverviewAudioDocuments) || (_migrated && !_migrated->overviewLoaded(OverviewAudioDocuments))) {
+		if (!_history->overviewLoaded(OverviewMusicFiles) || (_migrated && !_migrated->overviewLoaded(OverviewMusicFiles))) {
 			if (App::main()) {
-				if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(OverviewAudioDocuments))) {
-					App::main()->loadMediaBack(_migrated->peer, OverviewAudioDocuments);
+				if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(OverviewMusicFiles))) {
+					App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles);
 				} else {
-					App::main()->loadMediaBack(_history->peer, OverviewAudioDocuments);
-					if (_migrated && _index == 0 && _migrated->overview[OverviewAudioDocuments].isEmpty() && !_migrated->overviewLoaded(OverviewAudioDocuments)) {
-						App::main()->loadMediaBack(_migrated->peer, OverviewAudioDocuments);
+					App::main()->loadMediaBack(_history->peer, OverviewMusicFiles);
+					if (_migrated && _index == 0 && _migrated->overview[OverviewMusicFiles].isEmpty() && !_migrated->overviewLoaded(OverviewMusicFiles)) {
+						App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles);
 					}
 				}
-				if (_msgmigrated && !_history->overviewCountLoaded(OverviewAudioDocuments)) {
-					App::main()->preloadOverview(_history->peer, OverviewAudioDocuments);
+				if (_msgmigrated && !_history->overviewCountLoaded(OverviewMusicFiles)) {
+					App::main()->preloadOverview(_history->peer, OverviewMusicFiles);
 				}
 			}
 		}
@@ -296,7 +296,7 @@ void PlayerWidget::findCurrent() {
 	_index = -1;
 	if (!_history) return;
 
-	const History::MediaOverview *o = &(_msgmigrated ? _migrated : _history)->overview[OverviewAudioDocuments];
+	const History::MediaOverview *o = &(_msgmigrated ? _migrated : _history)->overview[OverviewMusicFiles];
 	if ((_msgmigrated ? _migrated : _history)->channelId() == _song.msgId.channel) {
 		for (int i = 0, l = o->size(); i < l; ++i) {
 			if (o->at(i) == _song.msgId.msg) {
@@ -312,14 +312,14 @@ void PlayerWidget::preloadNext() {
 	if (_index < 0) return;
 
 	History *history = _msgmigrated ? _migrated : _history;
-	const History::MediaOverview *o = &history->overview[OverviewAudioDocuments];
+	const History::MediaOverview *o = &history->overview[OverviewMusicFiles];
 	HistoryItem *next = 0;
 	if (_index < o->size() - 1) {
 		next = App::histItemById(history->channelId(), o->at(_index + 1));
-	} else if (_msgmigrated && _index == o->size() - 1 && _history->overviewLoaded(OverviewAudioDocuments) && _history->overviewCount(OverviewAudioDocuments) > 0) {
-		next = App::histItemById(_history->channelId(), _history->overview[OverviewAudioDocuments].at(0));
-	} else if (_msgmigrated && _index == o->size() - 1 && !_history->overviewCountLoaded(OverviewAudioDocuments)) {
-		if (App::main()) App::main()->preloadOverview(_history->peer, OverviewAudioDocuments);
+	} else if (_msgmigrated && _index == o->size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0) {
+		next = App::histItemById(_history->channelId(), _history->overview[OverviewMusicFiles].at(0));
+	} else if (_msgmigrated && _index == o->size() - 1 && !_history->overviewCountLoaded(OverviewMusicFiles)) {
+		if (App::main()) App::main()->preloadOverview(_history->peer, OverviewMusicFiles);
 	}
 	if (next) {
 		if (HistoryDocument *document = static_cast<HistoryDocument*>(next->getMedia())) {
@@ -348,12 +348,12 @@ void PlayerWidget::clearSelection() {
 }
 
 void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) {
-	if (_history && (_history->peer == peer || (_migrated && _migrated->peer == peer)) && type == OverviewAudioDocuments) {
+	if (_history && (_history->peer == peer || (_migrated && _migrated->peer == peer)) && type == OverviewMusicFiles) {
 		_index = -1;
 		History *history = _msgmigrated ? _migrated : _history;
 		if (history->channelId() == _song.msgId.channel) {
-			for (int i = 0, l = history->overview[OverviewAudioDocuments].size(); i < l; ++i) {
-				if (history->overview[OverviewAudioDocuments].at(i) == _song.msgId.msg) {
+			for (int i = 0, l = history->overview[OverviewMusicFiles].size(); i < l; ++i) {
+				if (history->overview[OverviewMusicFiles].at(i) == _song.msgId.msg) {
 					_index = i;
 					preloadNext();
 					break;
@@ -476,7 +476,7 @@ void PlayerWidget::playPressed() {
 	audioPlayer()->currentState(&playing, &playingState);
 	if (playing == _song && !(playingState & AudioPlayerStoppedMask)) {
 		if (playingState == AudioPlayerPausing || playingState == AudioPlayerPaused || playingState == AudioPlayerPausedAtEnd) {
-			audioPlayer()->pauseresume(OverviewDocuments);
+			audioPlayer()->pauseresume(OverviewFiles);
 		}
 	} else {
 		audioPlayer()->play(_song);
@@ -492,7 +492,7 @@ void PlayerWidget::pausePressed() {
 	audioPlayer()->currentState(&playing, &playingState);
 	if (playing == _song && !(playingState & AudioPlayerStoppedMask)) {
 		if (playingState == AudioPlayerStarting || playingState == AudioPlayerResuming || playingState == AudioPlayerPlaying || playingState == AudioPlayerFinishing) {
-			audioPlayer()->pauseresume(OverviewDocuments);
+			audioPlayer()->pauseresume(OverviewFiles);
 		}
 	}
 }
@@ -504,7 +504,7 @@ void PlayerWidget::playPausePressed() {
 	AudioPlayerState playingState = AudioPlayerStopped;
 	audioPlayer()->currentState(&playing, &playingState);
 	if (playing == _song && !(playingState & AudioPlayerStoppedMask)) {
-		audioPlayer()->pauseresume(OverviewDocuments);
+		audioPlayer()->pauseresume(OverviewFiles);
 	} else {
 		audioPlayer()->play(_song);
 		if (App::main()) App::main()->documentPlayProgress(_song);
@@ -515,11 +515,11 @@ void PlayerWidget::prevPressed() {
 	if (isHidden()) return;
 
 	History *history = _msgmigrated ? _migrated : _history;
-	const History::MediaOverview *o = history ? &history->overview[OverviewAudioDocuments] : 0;
+	const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0;
 	if (audioPlayer() && o && _index > 0 && _index <= o->size() && !o->isEmpty()) {
 		startPlay(FullMsgId(history->channelId(), o->at(_index - 1)));
 	} else if (!_index && _history && _migrated && !_msgmigrated) {
-		o = &_migrated->overview[OverviewAudioDocuments];
+		o = &_migrated->overview[OverviewMusicFiles];
 		if (!o->isEmpty()) {
 			startPlay(FullMsgId(_migrated->channelId(), o->at(o->size() - 1)));
 		}
@@ -530,11 +530,11 @@ void PlayerWidget::nextPressed() {
 	if (isHidden()) return;
 
 	History *history = _msgmigrated ? _migrated : _history;
-	const History::MediaOverview *o = history ? &history->overview[OverviewAudioDocuments] : 0;
+	const History::MediaOverview *o = history ? &history->overview[OverviewMusicFiles] : 0;
 	if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) {
 		startPlay(FullMsgId(history->channelId(), o->at(_index + 1)));
-	} else if (o && (_index == o->size() - 1) && _msgmigrated && _history->overviewLoaded(OverviewAudioDocuments)) {
-		o = &_history->overview[OverviewAudioDocuments];
+	} else if (o && (_index == o->size() - 1) && _msgmigrated && _history->overviewLoaded(OverviewMusicFiles)) {
+		o = &_history->overview[OverviewMusicFiles];
 		if (!o->isEmpty()) {
 			startPlay(FullMsgId(_history->channelId(), o->at(0)));
 		}
@@ -544,7 +544,7 @@ void PlayerWidget::nextPressed() {
 void PlayerWidget::stopPressed() {
 	if (!_song || isHidden()) return;
 
-	audioPlayer()->stop(OverviewDocuments);
+	audioPlayer()->stop(OverviewFiles);
 	if (App::main()) App::main()->hidePlayer();
 }
 
@@ -636,7 +636,7 @@ void PlayerWidget::updateState(SongMsgId playing, AudioPlayerState playingState,
 		display = _song.song->song()->duration;
 	}
 	bool showPause = false, stopped = ((playingState & AudioPlayerStoppedMask) || playingState == AudioPlayerFinishing);
-	bool wasPlaying = !!_duration;
+	bool wasPlaying = (_duration != 0);
 	if (!stopped) {
 		showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
 	}
diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp
index a98dee950..313709f18 100644
--- a/Telegram/SourceFiles/profilewidget.cpp
+++ b/Telegram/SourceFiles/profilewidget.cpp
@@ -101,7 +101,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData
 , _kickOver(0)
 , _kickDown(0)
 , _kickConfirm(0)
-	
+
 , _menu(0) {
 	connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
 
@@ -209,9 +209,9 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, PeerData
 	// shared media
 	connect((_mediaButtons[OverviewPhotos] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaPhotos()));
 	connect((_mediaButtons[OverviewVideos] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaVideos()));
-	connect((_mediaButtons[OverviewAudioDocuments] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaSongs()));
-	connect((_mediaButtons[OverviewDocuments] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaDocuments()));
-	connect((_mediaButtons[OverviewAudios] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaAudios()));
+	connect((_mediaButtons[OverviewMusicFiles] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaSongs()));
+	connect((_mediaButtons[OverviewFiles] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaDocuments()));
+	connect((_mediaButtons[OverviewVoiceFiles] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaAudios()));
 	connect((_mediaButtons[OverviewLinks] = new LinkButton(this, QString())), SIGNAL(clicked()), this, SLOT(onMediaLinks()));
 	updateMediaLinks();
 
@@ -261,7 +261,7 @@ void ProfileInner::loadProfilePhotos(int32 yFrom) {
 	int32 yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5;
 	MTP::clearLoaderPriorities();
 
-	int32 partfrom = _mediaButtons[OverviewAudios]->y() + _mediaButtons[OverviewAudios]->height() + st::profileHeaderSkip;
+	int32 partfrom = _mediaButtons[OverviewVoiceFiles]->y() + _mediaButtons[OverviewVoiceFiles]->height() + st::profileHeaderSkip;
 	yFrom -= partfrom;
 	yTo -= partfrom;
 
@@ -279,7 +279,7 @@ void ProfileInner::loadProfilePhotos(int32 yFrom) {
 void ProfileInner::onUpdatePhoto() {
 	saveError();
 
-	QStringList imgExtensions(cImgExtensions());	
+	QStringList imgExtensions(cImgExtensions());
 	QString filter(qsl("Image files (*") + imgExtensions.join(qsl(" *")) + qsl(");;All files (*.*)"));
 
 	QImage img;
@@ -440,15 +440,15 @@ void ProfileInner::onMediaVideos() {
 }
 
 void ProfileInner::onMediaSongs() {
-	App::main()->showMediaOverview(_peer, OverviewAudioDocuments);
+	App::main()->showMediaOverview(_peer, OverviewMusicFiles);
 }
 
 void ProfileInner::onMediaDocuments() {
-	App::main()->showMediaOverview(_peer, OverviewDocuments);
+	App::main()->showMediaOverview(_peer, OverviewFiles);
 }
 
 void ProfileInner::onMediaAudios() {
-	App::main()->showMediaOverview(_peer, OverviewAudios);
+	App::main()->showMediaOverview(_peer, OverviewVoiceFiles);
 }
 
 void ProfileInner::onMediaLinks() {
@@ -464,7 +464,7 @@ void ProfileInner::onInvitationLink() {
 
 void ProfileInner::onPublicLink() {
 	if (!_peerChannel) return;
-	
+
 	if (_peerChannel->isPublic()) {
 		QApplication::clipboard()->setText(qsl("https://telegram.me/") + _peerChannel->username);
 		Ui::showLayer(new InformBox(lang(lng_channel_public_link_copied)));
@@ -776,7 +776,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 			p.setOpacity(1);
 		}
 	}
-	
+
 	int32 namew = _width - st::profilePhotoSize - st::profileNameLeft;
 	p.setPen(st::black->p);
 	if (_peer->isVerified()) {
@@ -817,7 +817,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
 	top += st::profilePhotoSize;
 	top += st::profileButtonTop;
 
-	if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->amEditor() && _peerChannel->isMegagroup()))) {
+	if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->canAddParticipants() && _peerChannel->isMegagroup()))) {
 		top += _shareContact.height();
 	} else {
 		top -= st::profileButtonTop;
@@ -1271,7 +1271,7 @@ void ProfileInner::resizeEvent(QResizeEvent *e) {
 	_left = (width() - _width) / 2;
 
 	int32 top = 0, btnWidth = (_width - st::profileButtonSkip) / 2;
-	
+
 	// profile
 	top += st::profilePadding.top();
 	int32 addbyname = 0;
@@ -1294,13 +1294,17 @@ void ProfileInner::resizeEvent(QResizeEvent *e) {
 	top += st::profileButtonTop;
 
 	_uploadPhoto.setGeometry(_left, top, btnWidth, _uploadPhoto.height());
-	_addParticipant.setGeometry(_left + _width - btnWidth, top, btnWidth, _addParticipant.height());
+	if (_peerChannel && _peerChannel->count < cMaxMegaGroupCount() && _peerChannel->isMegagroup() && !_amCreator && !_peerChannel->amEditor() && _peerChannel->canAddParticipants()) {
+		_addParticipant.setGeometry(_left, top, btnWidth, _addParticipant.height());
+	} else {
+		_addParticipant.setGeometry(_left + _width - btnWidth, top, btnWidth, _addParticipant.height());
+	}
 
 	_sendMessage.setGeometry(_left, top, btnWidth, _sendMessage.height());
 	_shareContact.setGeometry(_left + _width - btnWidth, top, btnWidth, _shareContact.height());
 	_inviteToGroup.setGeometry(_left + _width - btnWidth, top, btnWidth, _inviteToGroup.height());
 
-	if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->amEditor() && _peerChannel->isMegagroup()))) {
+	if ((!_peerChat || _peerChat->canEdit()) && (!_peerChannel || _amCreator || (_peerChannel->canAddParticipants() && _peerChannel->isMegagroup()))) {
 		top += _shareContact.height();
 	} else {
 		top -= st::profileButtonTop;
@@ -1446,7 +1450,7 @@ ProfileInner::~ProfileInner() {
 	}
 	_participantsData.clear();
 }
-	
+
 void ProfileInner::openContextImage() {
 }
 
@@ -1634,7 +1638,7 @@ void ProfileInner::showAll() {
 				_invitationLink.hide();
 			}
 		}
-		if (_peerChannel->count < cMaxMegaGroupCount() && _peerChannel->isMegagroup() && (_amCreator || _peerChannel->amEditor())) {
+		if (_peerChannel->count < cMaxMegaGroupCount() && _peerChannel->isMegagroup() && _peerChannel->canAddParticipants()) {
 			_addParticipant.show();
 		} else {
 			_addParticipant.hide();
@@ -1711,9 +1715,9 @@ QString ProfileInner::overviewLinkText(int32 type, int32 count) {
 	switch (type) {
 	case OverviewPhotos: return lng_profile_photos(lt_count, count);
 	case OverviewVideos: return lng_profile_videos(lt_count, count);
-	case OverviewAudioDocuments: return lng_profile_songs(lt_count, count);
-	case OverviewDocuments: return lng_profile_files(lt_count, count);
-	case OverviewAudios: return lng_profile_audios(lt_count, count);
+	case OverviewMusicFiles: return lng_profile_songs(lt_count, count);
+	case OverviewFiles: return lng_profile_files(lt_count, count);
+	case OverviewVoiceFiles: return lng_profile_audios(lt_count, count);
 	case OverviewLinks: return lng_profile_shared_links(lt_count, count);
 	}
 	return QString();
diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp
index b29ff4c1f..044827de6 100644
--- a/Telegram/SourceFiles/pspecific_wnd.cpp
+++ b/Telegram/SourceFiles/pspecific_wnd.cpp
@@ -2475,48 +2475,6 @@ BOOL __stdcall ReadProcessMemoryRoutine64(
 	return bRet;
 }
 
-HANDLE _generateDumpFileAtPath(const WCHAR *path) {
-	static const int maxFileLen = MAX_PATH * 10;
-
-	WCHAR szPath[maxFileLen];
-	wsprintf(szPath, L"%stdumps\\", path);
-
-    if (!CreateDirectory(szPath, NULL)) {
-		DWORD errCode = GetLastError();
-		if (errCode && errCode != ERROR_ALREADY_EXISTS) {
-			return 0;
-		}
-	}
-
-	WCHAR szFileName[maxFileLen];
-	WCHAR szExeName[maxFileLen];
-
-	wcscpy_s(szExeName, _exeName);
-	WCHAR *dotFrom = wcschr(szExeName, WCHAR(L'.'));
-	if (dotFrom) {
-		wsprintf(dotFrom, L"");
-	}
-
-    SYSTEMTIME stLocalTime;
-
-    GetLocalTime(&stLocalTime);
-
-	if (cBetaVersion()) {
-		wsprintf(szFileName, L"%s%s-%ld-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
-				 szPath, szExeName, cBetaVersion(),
-				 stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
-				 stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
-				 GetCurrentProcessId(), GetCurrentThreadId());
-	} else {
-		wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
-				 szPath, szExeName, AppVersionStr,
-				 stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
-				 stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
-				 GetCurrentProcessId(), GetCurrentThreadId());
-	}
-    return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
-}
-
 // **************************************** ToolHelp32 ************************
 #define MAX_MODULE_NAME32 255
 #define TH32CS_SNAPMODULE   0x00000008
diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp
index db43d2337..6a9f25ed9 100644
--- a/Telegram/SourceFiles/structs.cpp
+++ b/Telegram/SourceFiles/structs.cpp
@@ -787,325 +787,6 @@ QString saveFileName(const QString &title, const QString &filter, const QString
 	return name;
 }
 
-void VideoOpenLink::onClick(Qt::MouseButton button) const {
-	if (button != Qt::LeftButton) return;
-	VideoData *data = video();
-
-	if (!data->date) return;
-
-	HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0);
-
-	const FileLocation &location(data->location(true));
-	if (!location.isEmpty()) {
-		psOpenFile(location.name());
-		if (App::main()) App::main()->videoMarkRead(data);
-		return;
-	}
-
-	if (data->status != FileReady) return;
-
-	QString filename;
-	if (!data->saveToCache()) {
-		filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false);
-		if (filename.isEmpty()) return;
-	}
-
-	data->save(filename, ActionOnLoadOpen, item ? item->fullId() : FullMsgId());
-}
-
-void VideoSaveLink::doSave(VideoData *data, bool forceSavingAs) {
-	if (!data->date) return;
-
-	QString already = data->already(true);
-	bool openWith = !already.isEmpty();
-	if (openWith && !forceSavingAs) {
-		QPoint pos(QCursor::pos());
-		if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
-			psOpenFile(already, true);
-		}
-	} else {
-		QFileInfo alreadyInfo(already);
-		QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir());
-		QString name = already.isEmpty() ? QString(".mov") : alreadyInfo.fileName();
-		QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), name, forceSavingAs, alreadyDir);
-		if (!filename.isEmpty()) {
-			ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith;
-			FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId());
-			data->save(filename, action, actionMsgId);
-		}
-	}
-}
-
-void VideoSaveLink::onClick(Qt::MouseButton button) const {
-	if (button != Qt::LeftButton) return;
-	doSave(video());
-}
-
-void VideoCancelLink::onClick(Qt::MouseButton button) const {
-	VideoData *data = video();
-	if (!data->date || button != Qt::LeftButton) return;
-
-	data->cancel();
-}
-
-VideoData::VideoData(const VideoId &id, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size)
-: id(id)
-, access(access)
-, date(date)
-, duration(duration)
-, w(w)
-, h(h)
-, thumb(thumb)
-, dc(dc)
-, size(size)
-, status(FileReady)
-, uploadOffset(0)
-, _actionOnLoad(ActionOnLoadNone)
-, _loader(0) {
-	_location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id));
-}
-
-void VideoData::forget() {
-	replyPreview->forget();
-	thumb->forget();
-}
-
-void VideoData::performActionOnLoad() {
-	if (_actionOnLoad == ActionOnLoadNone) return;
-
-	const FileLocation &loc(location(true));
-	QString already = loc.name();
-	if (already.isEmpty()) return;
-
-	if (_actionOnLoad == ActionOnLoadOpenWith) {
-		QPoint pos(QCursor::pos());
-		if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
-			psOpenFile(already, true);
-		}
-	} else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) {
-		psOpenFile(already);
-	}
-	_actionOnLoad = ActionOnLoadNone;
-}
-
-bool VideoData::loaded(bool check) const {
-	if (loading() && _loader->done()) {
-		if (_loader->fileType() == mtpc_storage_fileUnknown) {
-			_loader->deleteLater();
-			_loader->rpcInvalidate();
-			_loader = CancelledMtpFileLoader;
-		} else {
-			VideoData *that = const_cast<VideoData*>(this);
-			that->_location = FileLocation(mtpToStorageType(_loader->fileType()), _loader->fileName());
-
-			_loader->deleteLater();
-			_loader->rpcInvalidate();
-			_loader = 0;
-		}
-		notifyLayoutChanged();
-	}
-	return !already(check).isEmpty();
-}
-
-bool VideoData::loading() const {
-	return _loader && _loader != CancelledMtpFileLoader;
-}
-
-bool VideoData::displayLoading() const {
-	return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading();
-}
-
-float64 VideoData::progress() const {
-	if (uploading()) {
-		if (size > 0) {
-			return float64(uploadOffset) / size;
-		}
-		return 0;
-	}
-	return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0);
-}
-
-int32 VideoData::loadOffset() const {
-	return loading() ? _loader->currentOffset() : 0;
-}
-
-bool VideoData::uploading() const {
-	return status == FileUploading;
-}
-
-void VideoData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) {
-	if (loaded(true)) {
-		const FileLocation &l(location(true));
-		if (!toFile.isEmpty()) {
-			if (l.accessEnable()) {
-				QFile(l.name()).copy(toFile);
-				l.accessDisable();
-			}
-		}
-		return;
-	}
-
-	if (_loader == CancelledMtpFileLoader) _loader = 0;
-	if (_loader) {
-		if (!_loader->setFileName(toFile)) {
-			cancel();
-			_loader = 0;
-		}
-	}
-
-	_actionOnLoad = action;
-	_actionOnLoadMsgId = actionMsgId;
-
-	if (_loader) {
-		if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud();
-	} else {
-		status = FileReady;
-		_loader = new mtpFileLoader(dc, id, access, VideoFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading);
-		_loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(videoLoadProgress(FileLoader*)));
-		_loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(videoLoadFailed(FileLoader*,bool)));
-		_loader->start();
-	}
-
-	notifyLayoutChanged();
-}
-
-void VideoData::cancel() {
-	if (!loading()) return;
-
-	mtpFileLoader *l = _loader;
-	_loader = CancelledMtpFileLoader;
-	if (l) {
-		l->cancel();
-		l->deleteLater();
-		l->rpcInvalidate();
-
-		notifyLayoutChanged();
-	}
-	_actionOnLoad = ActionOnLoadNone;
-}
-
-void VideoData::notifyLayoutChanged() const {
-	const VideoItems &items(App::videoItems());
-	VideoItems::const_iterator i = items.constFind(const_cast<VideoData*>(this));
-	if (i != items.cend()) {
-		for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
-			Notify::historyItemLayoutChanged(j.key());
-		}
-	}
-}
-
-QString VideoData::already(bool check) const {
-	return location(check).name();
-}
-
-QByteArray VideoData::data() const {
-	return QByteArray();
-}
-
-const FileLocation &VideoData::location(bool check) const {
-	if (check && !_location.check()) {
-		const_cast<VideoData*>(this)->_location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id));
-	}
-	return _location;
-}
-
-void VideoData::setLocation(const FileLocation &loc) {
-	if (loc.check()) {
-		_location = loc;
-	}
-}
-
-void AudioOpenLink::onClick(Qt::MouseButton button) const {
-	if (button != Qt::LeftButton) return;
-	AudioData *data = audio();
-
-	if (!data->date) return;
-
-	HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0);
-
-	bool play = audioPlayer() && item;
-	const FileLocation &location(data->location(true));
-	if (!location.isEmpty() || (!data->data().isEmpty() && play)) {
-		if (play) {
-			AudioMsgId playing;
-			AudioPlayerState playingState = AudioPlayerStopped;
-			audioPlayer()->currentState(&playing, &playingState);
-			if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
-				audioPlayer()->pauseresume(OverviewAudios);
-			} else {
-				AudioMsgId audio(data, item->fullId());
-				audioPlayer()->play(audio);
-				if (App::main()) {
-					App::main()->audioPlayProgress(audio);
-					App::main()->audioMarkRead(data);
-				}
-			}
-		} else {
-			psOpenFile(location.name());
-			if (App::main()) App::main()->audioMarkRead(data);
-		}
-		return;
-	}
-
-	if (data->status != FileReady) return;
-
-	QString filename;
-	if (!data->saveToCache()) {
-		bool mp3 = (data->mime == qstr("audio/mp3"));
-		filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
-
-		if (filename.isEmpty()) return;
-	}
-
-	data->save(filename, ActionOnLoadOpen, item ? item->fullId() : FullMsgId());
-}
-
-void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) {
-	if (!data->date) return;
-
-	QString already = data->already(true);
-	bool openWith = !already.isEmpty();
-	if (openWith && !forceSavingAs) {
-		QPoint pos(QCursor::pos());
-		if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
-			psOpenFile(already, true);
-		}
-	} else {
-		QFileInfo alreadyInfo(already);
-		QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir());
-		bool mp3 = (data->mime == qstr("audio/mp3"));
-		QString name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : alreadyInfo.fileName();
-		QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), name, forceSavingAs, alreadyDir);
-		if (!filename.isEmpty()) {
-			ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith;
-			FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId());
-			data->save(filename, action, actionMsgId);
-		}
-	}
-}
-
-void AudioSaveLink::onClick(Qt::MouseButton button) const {
-	if (button != Qt::LeftButton) return;
-	doSave(audio());
-}
-
-void AudioCancelLink::onClick(Qt::MouseButton button) const {
-	AudioData *data = audio();
-	if (!data->date || button != Qt::LeftButton) return;
-
-	if (data->uploading()) {
-		HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0);
-		if (HistoryMessage *msg = item->toHistoryMessage()) {
-			if (msg->getMedia() && msg->getMedia()->type() == MediaTypeAudio && static_cast<HistoryAudio*>(msg->getMedia())->audio() == data) {
-				App::contextItem(item);
-				App::main()->deleteLayer(-2);
-			}
-		}
-	} else {
-		data->cancel();
-	}
-}
-
 bool StickerData::setInstalled() const {
 	switch (set.type()) {
 	case mtpc_inputStickerSetID: {
@@ -1124,217 +805,37 @@ bool StickerData::setInstalled() const {
 	return false;
 }
 
-AudioData::AudioData(const AudioId &id, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size)
-: id(id)
-, access(access)
-, date(date)
-, mime(mime)
-, duration(duration)
-, dc(dc)
-, size(size)
-, status(FileReady)
-, uploadOffset(0)
-, _actionOnLoad(ActionOnLoadNone)
-, _loader(0) {
-	_location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id));
-}
-
-bool AudioData::saveToCache() const {
-	return size < AudioVoiceMsgInMemory;
-}
-
-void AudioData::forget() {
-	_data.clear();
-}
-
-void AudioData::automaticLoad(const HistoryItem *item) {
-	if (loaded() || status != FileReady) return;
-
-	if (saveToCache() && _loader != CancelledMtpFileLoader) {
-		if (item) {
-			bool loadFromCloud = false;
-			if (item->history()->peer->isUser()) {
-				loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate);
-			} else {
-				loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups);
-			}
-			save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true);
-		}
-	}
-}
-
-void AudioData::automaticLoadSettingsChanged() {
-	if (loaded() || status != FileReady || !saveToCache() || _loader != CancelledMtpFileLoader) return;
-	_loader = 0;
-}
-
-void AudioData::performActionOnLoad() {
-	if (_actionOnLoad == ActionOnLoadNone) return;
-
-	const FileLocation &loc(location(true));
-	QString already = loc.name();
-	bool play = _actionOnLoadMsgId.msg && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && audioPlayer();
-
-	if (play) {
-		if (loaded()) {
-			AudioMsgId playing;
-			AudioPlayerState state = AudioPlayerStopped;
-			audioPlayer()->currentState(&playing, &state);
-			if (playing.msgId == _actionOnLoadMsgId && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) {
-				audioPlayer()->pauseresume(OverviewAudios);
-			} else {
-				audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId));
-				if (App::main()) App::main()->audioMarkRead(this);
-			}
-		}
+QString documentSaveFilename(DocumentData *data, bool forceSavingAs = false, const QString already = QString(), const QDir &dir = QDir()) {
+	QString name, filter, caption, prefix;
+	MimeType mimeType = mimeTypeForName(data->mime);
+	QStringList p = mimeType.globPatterns();
+	QString pattern = p.isEmpty() ? QString() : p.front();
+	if (data->voice()) {
+		bool mp3 = (data->mime == qstr("audio/mp3"));
+		name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : already;
+		filter = mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)");
+		caption = lang(lng_save_audio);
+		prefix = qsl("audio");
+	} else if (data->isVideo()) {
+		name = already.isEmpty() ? qsl(".mov") : already;
+		filter = qsl("MOV Video (*.mov);;All files (*.*)");
+		caption = lang(lng_save_video);
+		prefix = qsl("video");
 	} else {
-		if (already.isEmpty()) return;
-		if (_actionOnLoad == ActionOnLoadOpenWith) {
-			if (already.isEmpty()) return;
-
-			QPoint pos(QCursor::pos());
-			if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
-				psOpenFile(already, true);
-			}
-			if (App::main()) App::main()->audioMarkRead(this);
-		} else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) {
-			psOpenFile(already);
-			if (App::main()) App::main()->audioMarkRead(this);
+		name = already.isEmpty() ? data->name : already;
+		if (name.isEmpty()) {
+			name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
 		}
-	}
-	_actionOnLoad = ActionOnLoadNone;
-}
-
-bool AudioData::loaded(bool check) const {
-	if (loading() && _loader->done()) {
-		if (_loader->fileType() == mtpc_storage_fileUnknown) {
-			_loader->deleteLater();
-			_loader->rpcInvalidate();
-			_loader = CancelledMtpFileLoader;
+		if (pattern.isEmpty()) {
+			filter = QString();
 		} else {
-			AudioData *that = const_cast<AudioData*>(this);
-			that->_location = FileLocation(mtpToStorageType(_loader->fileType()), _loader->fileName());
-			that->_data = _loader->bytes();
-
-			_loader->deleteLater();
-			_loader->rpcInvalidate();
-			_loader = 0;
+			filter = mimeType.filterString() + qsl(";;All files (*.*)");
 		}
-		notifyLayoutChanged();
-	}
-	return !_data.isEmpty() || !already(check).isEmpty();
-}
-
-bool AudioData::loading() const {
-	return _loader && _loader != CancelledMtpFileLoader;
-}
-
-bool AudioData::displayLoading() const {
-	return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading();
-}
-
-float64 AudioData::progress() const {
-	if (uploading()) {
-		if (size > 0) {
-			return float64(uploadOffset) / size;
-		}
-		return 0;
-	}
-	return loading() ? _loader->currentProgress() : (loaded() ? 1 : 0);
-}
-
-int32 AudioData::loadOffset() const {
-	return loading() ? _loader->currentOffset() : 0;
-}
-
-bool AudioData::uploading() const {
-	return status == FileUploading;
-}
-
-void AudioData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) {
-	if (loaded(true)) {
-		const FileLocation &l(location(true));
-		if (!toFile.isEmpty()) {
-			if (!_data.isEmpty()) {
-				QFile f(toFile);
-				f.open(QIODevice::WriteOnly);
-				f.write(_data);
-			} else if (l.accessEnable()) {
-				QFile(l.name()).copy(toFile);
-				l.accessDisable();
-			}
-		}
-		return;
+		caption = lang(lng_save_file);
+		prefix = qsl("doc");
 	}
 
-	if (_loader == CancelledMtpFileLoader) _loader = 0;
-	if (_loader) {
-		if (!_loader->setFileName(toFile)) {
-			cancel();
-			_loader = 0;
-		}
-	}
-
-	_actionOnLoad = action;
-	_actionOnLoadMsgId = actionMsgId;
-
-	if (_loader) {
-		if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud();
-	} else {
-		status = FileReady;
-		_loader = new mtpFileLoader(dc, id, access, AudioFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading);
-		_loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(audioLoadProgress(FileLoader*)));
-		_loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(audioLoadFailed(FileLoader*,bool)));
-		_loader->start();
-	}
-
-	notifyLayoutChanged();
-}
-
-void AudioData::cancel() {
-	if (!loading()) return;
-
-	mtpFileLoader *l = _loader;
-	_loader = CancelledMtpFileLoader;
-	if (l) {
-		l->cancel();
-		l->deleteLater();
-		l->rpcInvalidate();
-
-		notifyLayoutChanged();
-	}
-	_actionOnLoad = ActionOnLoadNone;
-}
-
-void AudioData::notifyLayoutChanged() const {
-	const AudioItems &items(App::audioItems());
-	AudioItems::const_iterator i = items.constFind(const_cast<AudioData*>(this));
-	if (i != items.cend()) {
-		for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
-			Notify::historyItemLayoutChanged(j.key());
-		}
-	}
-}
-
-QString AudioData::already(bool check) const {
-	return location(check).name();
-}
-
-QByteArray AudioData::data() const {
-	return _data;
-}
-
-const FileLocation &AudioData::location(bool check) const {
-	if (check && !_location.check()) {
-		const_cast<AudioData*>(this)->_location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id));
-	}
-	return _location;
-}
-
-void AudioData::setLocation(const FileLocation &loc) {
-	if (loc.check()) {
-		_location = loc;
-	}
+	return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
 }
 
 void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) {
@@ -1342,21 +843,39 @@ void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) {
 
 	HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0);
 
+	bool playVoice = data->voice() && audioPlayer() && item;
 	bool playMusic = data->song() && audioPlayer() && item;
 	bool playAnimation = data->isAnimation() && item && item->getMedia();
 	const FileLocation &location(data->location(true));
-	if (!location.isEmpty() || (!data->data().isEmpty() && (playMusic || playAnimation))) {
-		if (playMusic) {
+	if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playAnimation))) {
+		if (playVoice) {
+			AudioMsgId playing;
+			AudioPlayerState playingState = AudioPlayerStopped;
+			audioPlayer()->currentState(&playing, &playingState);
+			if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
+				audioPlayer()->pauseresume(OverviewVoiceFiles);
+			} else {
+				AudioMsgId audio(data, item->fullId());
+				audioPlayer()->play(audio);
+				if (App::main()) {
+					App::main()->audioPlayProgress(audio);
+					App::main()->mediaMarkRead(data);
+				}
+			}
+		} else if (playMusic) {
 			SongMsgId playing;
 			AudioPlayerState playingState = AudioPlayerStopped;
 			audioPlayer()->currentState(&playing, &playingState);
 			if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
-				audioPlayer()->pauseresume(OverviewDocuments);
+				audioPlayer()->pauseresume(OverviewFiles);
 			} else {
 				SongMsgId song(data, item->fullId());
 				audioPlayer()->play(song);
 				if (App::main()) App::main()->documentPlayProgress(song);
 			}
+		} else if (data->voice() || data->isVideo()) {
+			psOpenFile(location.name());
+			if (App::main()) App::main()->mediaMarkRead(data);
 		} else if (data->size < MediaViewImageSizeLimit) {
 			if (!data->data().isEmpty() && playAnimation) {
 				if (action == ActionOnLoadPlayInline) {
@@ -1388,22 +907,7 @@ void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) {
 
 	QString filename;
 	if (!data->saveToCache()) {
-		QString name = data->name, filter;
-		MimeType mimeType = mimeTypeForName(data->mime);
-		QStringList p = mimeType.globPatterns();
-		QString pattern = p.isEmpty() ? QString() : p.front();
-		if (name.isEmpty()) {
-			name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
-		}
-
-		if (pattern.isEmpty()) {
-			filter = QString();
-		} else {
-			filter = mimeType.filterString() + qsl(";;All files (*.*)");
-		}
-
-		filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false);
-
+		filename = documentSaveFilename(data);
 		if (filename.isEmpty()) return;
 	}
 
@@ -1412,16 +916,17 @@ void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) {
 
 void DocumentOpenLink::onClick(Qt::MouseButton button) const {
 	if (button != Qt::LeftButton) return;
-	doOpen(document());
+	doOpen(document(), document()->voice() ? ActionOnLoadNone : ActionOnLoadOpen);
 }
 
-void GifOpenLink::doOpen(DocumentData *data) {
-	return DocumentOpenLink::doOpen(data, ActionOnLoadPlayInline);
+void VoiceSaveLink::onClick(Qt::MouseButton button) const {
+	if (button != Qt::LeftButton) return;
+	doOpen(document(), ActionOnLoadNone);
 }
 
 void GifOpenLink::onClick(Qt::MouseButton button) const {
 	if (button != Qt::LeftButton) return;
-	doOpen(document());
+	doOpen(document(), ActionOnLoadPlayInline);
 }
 
 void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) {
@@ -1437,21 +942,8 @@ void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) {
 	} else {
 		QFileInfo alreadyInfo(already);
 		QDir alreadyDir(already.isEmpty() ? QDir() : alreadyInfo.dir());
-		QString name = already.isEmpty() ? data->name : alreadyInfo.fileName(), filter;
-		MimeType mimeType = mimeTypeForName(data->mime);
-		QStringList p = mimeType.globPatterns();
-		QString pattern = p.isEmpty() ? QString() : p.front();
-		if (name.isEmpty()) {
-			name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
-		}
-
-		if (pattern.isEmpty()) {
-			filter = QString();
-		} else {
-			filter = mimeType.filterString() + qsl(";;All files (*.*)");
-		}
-
-		QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, forceSavingAs, alreadyDir);
+		QString alreadyName(already.isEmpty() ? QString() : alreadyInfo.fileName());
+		QString filename = documentSaveFilename(data, forceSavingAs, alreadyName, alreadyDir);
 		if (!filename.isEmpty()) {
 			ActionOnLoad action = already.isEmpty() ? ActionOnLoadNone : ActionOnLoadOpenWith;
 			FullMsgId actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId());
@@ -1484,6 +976,14 @@ void DocumentCancelLink::onClick(Qt::MouseButton button) const {
 	}
 }
 
+VoiceData::~VoiceData() {
+	if (!waveform.isEmpty() && waveform.at(0) == -1 && waveform.size() > sizeof(TaskId)) {
+		TaskId taskId = 0;
+		memcpy(&taskId, waveform.constData() + 1, sizeof(taskId));
+		Local::cancelTask(taskId);
+	}
+}
+
 DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : id(id)
 , type(FileDocument)
 , access(access)
@@ -1498,8 +998,8 @@ DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 dat
 , _duration(-1)
 , _actionOnLoad(ActionOnLoadNone)
 , _loader(0) {
-	_location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id));
 	setattributes(attributes);
+	_location = Local::readFileLocation(mediaKey());
 }
 
 void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes) {
@@ -1537,11 +1037,27 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
 		case mtpc_documentAttributeAudio: {
 			const MTPDdocumentAttributeAudio &d(attributes[i].c_documentAttributeAudio());
 			if (type == FileDocument) {
-				type = SongDocument;
-				SongData *song = new SongData();
-				_additional = song;
+				if (d.is_voice()) {
+					type = VoiceDocument;
+					VoiceData *voice = new VoiceData();
+					_additional = voice;
+				} else {
+					type = SongDocument;
+					SongData *song = new SongData();
+					_additional = song;
+				}
 			}
-			if (song()) {
+			if (voice()) {
+				voice()->duration = d.vduration.v;
+				VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform));
+				uchar wavemax = 0;
+				for (int32 i = 0, l = waveform.size(); i < l; ++i) {
+					uchar waveat = waveform.at(i);
+					if (wavemax < waveat) wavemax = waveat;
+				}
+				voice()->waveform = waveform;
+				voice()->wavemax = wavemax;
+			} else if (song()) {
 				song()->duration = d.vduration.v;
 				song()->title = qs(d.vtitle);
 				song()->performer = qs(d.vperformer);
@@ -1560,7 +1076,7 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
 }
 
 bool DocumentData::saveToCache() const {
-	return (type == StickerDocument) || (isAnimation() && size < AnimationInMemory);
+	return (type == StickerDocument) || (isAnimation() && size < AnimationInMemory) || (voice() && size < AudioVoiceMsgInMemory);
 }
 
 void DocumentData::forget() {
@@ -1588,12 +1104,22 @@ void DocumentData::automaticLoad(const HistoryItem *item) {
 				loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups);
 			}
 			save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true);
+		} else if (voice()) {
+			if (item) {
+				bool loadFromCloud = false;
+				if (item->history()->peer->isUser()) {
+					loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate);
+				} else {
+					loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups);
+				}
+				save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true);
+			}
 		}
 	}
 }
 
 void DocumentData::automaticLoadSettingsChanged() {
-	if (loaded() || status != FileReady || !isAnimation() || !saveToCache() || _loader != CancelledMtpFileLoader) return;
+	if (loaded() || status != FileReady || (!isAnimation() && !voice()) || !saveToCache() || _loader != CancelledMtpFileLoader) return;
 	_loader = 0;
 }
 
@@ -1603,16 +1129,29 @@ void DocumentData::performActionOnLoad() {
 	const FileLocation &loc(location(true));
 	QString already = loc.name();
 	HistoryItem *item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : 0;
-	bool showImage = item && (size < MediaViewImageSizeLimit);
+	bool showImage = !isVideo() && item && (size < MediaViewImageSizeLimit);
+	bool playVoice = voice() && audioPlayer() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && item;
 	bool playMusic = song() && audioPlayer() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && item;
 	bool playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item->getMedia();
-	if (playMusic) {
+	if (playVoice) {
+		if (loaded()) {
+			AudioMsgId playing;
+			AudioPlayerState state = AudioPlayerStopped;
+			audioPlayer()->currentState(&playing, &state);
+			if (playing.msgId == _actionOnLoadMsgId && !(state & AudioPlayerStoppedMask) && state != AudioPlayerFinishing) {
+				audioPlayer()->pauseresume(OverviewVoiceFiles);
+			} else {
+				audioPlayer()->play(AudioMsgId(this, _actionOnLoadMsgId));
+				if (App::main()) App::main()->mediaMarkRead(this);
+			}
+		}
+	} else if (playMusic) {
 		if (loaded()) {
 			SongMsgId playing;
 			AudioPlayerState playingState = AudioPlayerStopped;
 			audioPlayer()->currentState(&playing, &playingState);
 			if (playing.msgId == item->fullId() && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
-				audioPlayer()->pauseresume(OverviewDocuments);
+				audioPlayer()->pauseresume(OverviewFiles);
 			} else {
 				SongMsgId song(this, item->fullId());
 				audioPlayer()->play(song);
@@ -1631,14 +1170,15 @@ void DocumentData::performActionOnLoad() {
 		if (already.isEmpty()) return;
 
 		if (_actionOnLoad == ActionOnLoadOpenWith) {
-			if (already.isEmpty()) return;
-
 			QPoint pos(QCursor::pos());
 			if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) {
 				psOpenFile(already, true);
 			}
 		} else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) {
-			if (loc.accessEnable()) {
+			if (voice() || isVideo()) {
+				psOpenFile(already);
+				if (App::main()) App::main()->mediaMarkRead(this);
+			} else if (loc.accessEnable()) {
 				if (showImage && QImageReader(loc.name()).canRead()) {
 					if (_actionOnLoad == ActionOnLoadPlayInline) {
 						item->getMedia()->playInline(item);
@@ -1737,12 +1277,12 @@ void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMs
 		if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud();
 	} else {
 		status = FileReady;
-		_loader = new mtpFileLoader(dc, id, access, DocumentFileLocation, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading);
+		LocationType type = voice() ? AudioFileLocation : (isVideo() ? VideoFileLocation : DocumentFileLocation);
+		_loader = new mtpFileLoader(dc, id, access, type, toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading);
 		_loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*)));
 		_loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool)));
 		_loader->start();
 	}
-
 	notifyLayoutChanged();
 }
 
@@ -1771,6 +1311,24 @@ void DocumentData::notifyLayoutChanged() const {
 	}
 }
 
+VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) {
+	VoiceWaveform result((encoded5bit.size() * 8) / 5, 0);
+	for (int32 i = 0, l = result.size(); i < l; ++i) { // read each 5 bit of encoded5bit as 0-31 unsigned char
+		int32 byte = (i * 5) / 8, shift = (i * 5) % 8;
+		result[i] = (((*(uint16*)(encoded5bit.constData() + byte)) >> shift) & 0x1F);
+	}
+	return result;
+}
+
+QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) {
+	QByteArray result((waveform.size() * 5 + 7) / 8, 0);
+	for (int32 i = 0, l = waveform.size(); i < l; ++i) { // write each 0-31 unsigned char as 5 bit to result
+		int32 byte = (i * 5) / 8, shift = (i * 5) % 8;
+		(*(uint16*)(result.data() + byte)) |= (uint16(waveform.at(i) & 0x1F) << shift);
+	}
+	return result;
+}
+
 QString DocumentData::already(bool check) const {
 	if (check && _location.name().isEmpty()) return QString();
 	return location(check).name();
@@ -1782,7 +1340,7 @@ QByteArray DocumentData::data() const {
 
 const FileLocation &DocumentData::location(bool check) const {
 	if (check && !_location.check()) {
-		const_cast<DocumentData*>(this)->_location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id));
+		const_cast<DocumentData*>(this)->_location = Local::readFileLocation(mediaKey());
 	}
 	return _location;
 }
@@ -1827,7 +1385,7 @@ bool fileIsImage(const QString &name, const QString &mime) {
 }
 
 void DocumentData::recountIsImage() {
-	if (isAnimation() || type == VideoDocument) return;
+	if (isAnimation() || isVideo()) return;
 	_duration = fileIsImage(name, mime) ? 1 : -1; // hack
 }
 
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 78e899e56..625b300db 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -626,6 +626,9 @@ public:
 	bool isVerified() const {
 		return flags & MTPDchannel::flag_verified;
 	}
+	bool canAddParticipants() const {
+		return amCreator() || amEditor() || (flags & MTPDchannel::flag_invites_enabled);
+	}
 
 //	ImagePtr photoFull;
 	QString invitationUrl;
@@ -815,237 +818,13 @@ enum FileStatus {
 	FileReady = 1,
 };
 
-class VideoData {
-public:
-	VideoData(const VideoId &id, const uint64 &access = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0);
-
-	void automaticLoad(const HistoryItem *item) {
-	}
-	void automaticLoadSettingsChanged() {
-	}
-
-	bool loaded(bool check = false) const;
-	bool loading() const;
-	bool displayLoading() const;
-	void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false);
-	void cancel();
-	float64 progress() const;
-	int32 loadOffset() const;
-	bool uploading() const;
-
-	QString already(bool check = false) const;
-	QByteArray data() const;
-	const FileLocation &location(bool check = false) const;
-	void setLocation(const FileLocation &loc);
-
-	bool saveToCache() const {
-		return false;
-	}
-
-	void performActionOnLoad();
-
-	void forget();
-
-	VideoId id;
-	uint64 access;
-	int32 date;
-	int32 duration;
-	int32 w, h;
-	ImagePtr thumb, replyPreview;
-	int32 dc, size;
-	// geo, caption
-
-	FileStatus status;
-	int32 uploadOffset;
-
-private:
-	FileLocation _location;
-
-	ActionOnLoad _actionOnLoad;
-	FullMsgId _actionOnLoadMsgId;
-	mutable mtpFileLoader *_loader;
-
-	void notifyLayoutChanged() const;
-
-};
-
-class VideoLink : public ITextLink {
-	TEXT_LINK_CLASS(VideoLink)
-
-public:
-	VideoLink(VideoData *video) : _video(video) {
-	}
-	VideoData *video() const {
-		return _video;
-	}
-
-private:
-	VideoData *_video;
-
-};
-
-class VideoSaveLink : public VideoLink {
-	TEXT_LINK_CLASS(VideoSaveLink)
-
-public:
-	VideoSaveLink(VideoData *video) : VideoLink(video) {
-	}
-	static void doSave(VideoData *video, bool forceSavingAs = false);
-	void onClick(Qt::MouseButton button) const;
-};
-
-class VideoOpenLink : public VideoLink {
-	TEXT_LINK_CLASS(VideoOpenLink)
-
-public:
-	VideoOpenLink(VideoData *video) : VideoLink(video) {
-	}
-	void onClick(Qt::MouseButton button) const;
-
-};
-
-class VideoCancelLink : public VideoLink {
-	TEXT_LINK_CLASS(VideoCancelLink)
-
-public:
-	VideoCancelLink(VideoData *video) : VideoLink(video) {
-	}
-	void onClick(Qt::MouseButton button) const;
-
-};
-
-class AudioData {
-public:
-	AudioData(const AudioId &id, const uint64 &access = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0);
-
-	void automaticLoad(const HistoryItem *item); // auto load voice message
-	void automaticLoadSettingsChanged();
-
-	bool loaded(bool check = false) const;
-	bool loading() const;
-	bool displayLoading() const;
-	void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false);
-	void cancel();
-	float64 progress() const;
-	int32 loadOffset() const;
-	bool uploading() const;
-
-	QString already(bool check = false) const;
-	QByteArray data() const;
-	const FileLocation &location(bool check = false) const;
-	void setLocation(const FileLocation &loc);
-
-	bool saveToCache() const;
-
-	void performActionOnLoad();
-
-	void forget();
-	void setData(const QByteArray &data) {
-		_data = data;
-	}
-
-	AudioId id;
-	uint64 access;
-	int32 date;
-	QString mime;
-	int32 duration;
-	int32 dc;
-	int32 size;
-
-	FileStatus status;
-	int32 uploadOffset;
-
-	int32 md5[8];
-
-private:
-	FileLocation _location;
-	QByteArray _data;
-
-	ActionOnLoad _actionOnLoad;
-	FullMsgId _actionOnLoadMsgId;
-	mutable mtpFileLoader *_loader;
-
-	void notifyLayoutChanged() const;
-
-};
-
-struct AudioMsgId {
-	AudioMsgId() : audio(0) {
-	}
-	AudioMsgId(AudioData *audio, const FullMsgId &msgId) : audio(audio), msgId(msgId) {
-	}
-	AudioMsgId(AudioData *audio, ChannelId channelId, MsgId msgId) : audio(audio), msgId(channelId, msgId) {
-	}
-	operator bool() const {
-		return audio;
-	}
-	AudioData *audio;
-	FullMsgId msgId;
-
-};
-
-inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) {
-	return quintptr(a.audio) < quintptr(b.audio) || (quintptr(a.audio) == quintptr(b.audio) && a.msgId < b.msgId);
-}
-inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) {
-	return a.audio == b.audio && a.msgId == b.msgId;
-}
-inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) {
-	return !(a == b);
-}
-
-class AudioLink : public ITextLink {
-	TEXT_LINK_CLASS(AudioLink)
-
-public:
-	AudioLink(AudioData *audio) : _audio(audio) {
-	}
-	AudioData *audio() const {
-		return _audio;
-	}
-
-private:
-	AudioData *_audio;
-
-};
-
-class AudioSaveLink : public AudioLink {
-	TEXT_LINK_CLASS(AudioSaveLink)
-
-public:
-	AudioSaveLink(AudioData *audio) : AudioLink(audio) {
-	}
-	static void doSave(AudioData *audio, bool forceSavingAs = false);
-	void onClick(Qt::MouseButton button) const;
-
-};
-
-class AudioOpenLink : public AudioLink {
-	TEXT_LINK_CLASS(AudioOpenLink)
-
-public:
-	AudioOpenLink(AudioData *audio) : AudioLink(audio) {
-	}
-	void onClick(Qt::MouseButton button) const;
-
-};
-
-class AudioCancelLink : public AudioLink {
-	TEXT_LINK_CLASS(AudioCancelLink)
-
-public:
-	AudioCancelLink(AudioData *audio) : AudioLink(audio) {
-	}
-	void onClick(Qt::MouseButton button) const;
-
-};
-
 enum DocumentType {
 	FileDocument     = 0,
 	VideoDocument    = 1,
 	SongDocument     = 2,
 	StickerDocument  = 3,
 	AnimatedDocument = 4,
+	VoiceDocument    = 5,
 };
 
 struct DocumentAdditionalData {
@@ -1072,6 +851,16 @@ struct SongData : public DocumentAdditionalData {
 
 };
 
+typedef QVector<char> VoiceWaveform; // [0] == -1 -- counting, [0] == -2 -- could not count
+struct VoiceData : public DocumentAdditionalData {
+	VoiceData() : duration(0), wavemax(0) {
+	}
+	~VoiceData();
+	int32 duration;
+	VoiceWaveform waveform;
+	char wavemax;
+};
+
 bool fileIsImage(const QString &name, const QString &mime);
 
 class DocumentData {
@@ -1126,19 +915,34 @@ public:
 	SongData *song() {
 		return (type == SongDocument) ? static_cast<SongData*>(_additional) : 0;
 	}
+	VoiceData *voice() {
+		return (type == VoiceDocument) ? static_cast<VoiceData*>(_additional) : 0;
+	}
+	const VoiceData *voice() const {
+		return (type == VoiceDocument) ? static_cast<VoiceData*>(_additional) : 0;
+	}
 	bool isAnimation() const {
 		return (type == AnimatedDocument) || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive);
 	}
 	bool isGifv() const {
 		return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive);
 	}
+	bool isMusic() const {
+		return (type == SongDocument) ? !static_cast<SongData*>(_additional)->title.isEmpty() : false;
+	}
+	bool isVideo() const {
+		return (type == VideoDocument);
+	}
 	int32 duration() const {
-		return (isAnimation() || type == VideoDocument) ? _duration : -1;
+		return (isAnimation() || isVideo()) ? _duration : -1;
 	}
 	bool isImage() const {
-		return !isAnimation() && (type != VideoDocument) && (_duration > 0);
+		return !isAnimation() && !isVideo() && (_duration > 0);
 	}
 	void recountIsImage();
+	void setData(const QByteArray &data) {
+		_data = data;
+	}
 
 	~DocumentData();
 
@@ -1157,6 +961,11 @@ public:
 
 	int32 md5[8];
 
+	MediaKey mediaKey() const {
+		LocationType t = isVideo() ? VideoFileLocation : (voice() ? AudioFileLocation : DocumentFileLocation);
+		return ::mediaKey(t, dc, id);
+	}
+
 private:
 
 	FileLocation _location;
@@ -1172,6 +981,9 @@ private:
 
 };
 
+VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit);
+QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
+
 struct SongMsgId {
 	SongMsgId() : song(0) {
 	}
@@ -1196,6 +1008,31 @@ inline bool operator!=(const SongMsgId &a, const SongMsgId &b) {
 	return !(a == b);
 }
 
+struct AudioMsgId {
+	AudioMsgId() : audio(0) {
+	}
+	AudioMsgId(DocumentData *audio, const FullMsgId &msgId) : audio(audio), msgId(msgId) {
+	}
+	AudioMsgId(DocumentData *audio, ChannelId channelId, MsgId msgId) : audio(audio), msgId(channelId, msgId) {
+	}
+	operator bool() const {
+		return audio;
+	}
+	DocumentData *audio;
+	FullMsgId msgId;
+
+};
+
+inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) {
+	return quintptr(a.audio) < quintptr(b.audio) || (quintptr(a.audio) == quintptr(b.audio) && a.msgId < b.msgId);
+}
+inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) {
+	return a.audio == b.audio && a.msgId == b.msgId;
+}
+inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) {
+	return !(a == b);
+}
+
 class DocumentLink : public ITextLink {
 	TEXT_LINK_CLASS(DocumentLink)
 
@@ -1233,13 +1070,22 @@ public:
 
 };
 
+class VoiceSaveLink : public DocumentOpenLink {
+	TEXT_LINK_CLASS(VoiceSaveLink)
+
+public:
+	VoiceSaveLink(DocumentData *document) : DocumentOpenLink(document) {
+	}
+	void onClick(Qt::MouseButton button) const;
+
+};
+
 class GifOpenLink : public DocumentOpenLink {
 	TEXT_LINK_CLASS(GifOpenLink)
 
 public:
 	GifOpenLink(DocumentData *document) : DocumentOpenLink(document) {
 	}
-	static void doOpen(DocumentData *document);
 	void onClick(Qt::MouseButton button) const;
 
 };
diff --git a/Telegram/SourceFiles/types.cpp b/Telegram/SourceFiles/types.cpp
index b3fb139f2..497627b7a 100644
--- a/Telegram/SourceFiles/types.cpp
+++ b/Telegram/SourceFiles/types.cpp
@@ -1044,6 +1044,8 @@ const InterfacesMetadata *GetInterfacesMetadata(uint64 mask) {
 	return i.value();
 }
 
+const InterfacesMetadata *Interfaces::ZeroInterfacesMetadata = GetInterfacesMetadata(0);
+
 InterfaceWrapStruct InterfaceWraps[64];
 
 QAtomicInt InterfaceIndexLast(0);
diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h
index 4647bada8..a87d0fa67 100644
--- a/Telegram/SourceFiles/types.h
+++ b/Telegram/SourceFiles/types.h
@@ -532,18 +532,21 @@ inline void destroyImplementation(I *&ptr) {
 class Interfaces;
 typedef void(*InterfaceConstruct)(void *location, Interfaces *interfaces);
 typedef void(*InterfaceDestruct)(void *location);
+typedef void(*InterfaceAssign)(void *location, void *waslocation);
 
 struct InterfaceWrapStruct {
 	InterfaceWrapStruct() : Size(0), Construct(0), Destruct(0) {
 	}
-	InterfaceWrapStruct(int size, InterfaceConstruct construct, InterfaceDestruct destruct)
+	InterfaceWrapStruct(int size, InterfaceConstruct construct, InterfaceDestruct destruct, InterfaceAssign assign)
 	: Size(size)
 	, Construct(construct)
-	, Destruct(destruct) {
+	, Destruct(destruct)
+	, Assign(assign) {
 	}
 	int Size;
 	InterfaceConstruct Construct;
 	InterfaceDestruct Destruct;
+	InterfaceAssign Assign;
 };
 
 template <int Value, int Denominator>
@@ -560,6 +563,9 @@ struct InterfaceWrapTemplate {
 	static void Destruct(void *location) {
 		((Type*)location)->~Type();
 	}
+	static void Assign(void *location, void *waslocation) {
+		*((Type*)location) = *((Type*)waslocation);
+	}
 };
 
 extern InterfaceWrapStruct InterfaceWraps[64];
@@ -578,7 +584,7 @@ public:
 			if (InterfaceIndexLast.testAndSetOrdered(last, last + 1)) {
 				t_assert(last < 64);
 				if (_index.testAndSetOrdered(0, last + 1)) {
-					InterfaceWraps[last] = InterfaceWrapStruct(InterfaceWrapTemplate<Type>::Size, InterfaceWrapTemplate<Type>::Construct, InterfaceWrapTemplate<Type>::Destruct);
+					InterfaceWraps[last] = InterfaceWrapStruct(InterfaceWrapTemplate<Type>::Size, InterfaceWrapTemplate<Type>::Construct, InterfaceWrapTemplate<Type>::Destruct, InterfaceWrapTemplate<Type>::Assign);
 				}
 				break;
 			}
@@ -627,6 +633,10 @@ public:
 	int size, last;
 	int offsets[64];
 
+	bool equals(const uint64 &mask) const {
+		return _mask == mask;
+	}
+
 private:
 	uint64 _mask;
 
@@ -637,22 +647,26 @@ const InterfacesMetadata *GetInterfacesMetadata(uint64 mask);
 class Interfaces {
 public:
 
-	Interfaces(uint64 mask = 0) : _meta(GetInterfacesMetadata(mask)), _data(0) {
-		if (_meta->size) {
-			_data = malloc(_meta->size);
-			if (!_data) { // terminate if we can't allocate memory
+	Interfaces(uint64 mask = 0) : _data(zerodata()) {
+		if (mask) {
+			const InterfacesMetadata *meta = GetInterfacesMetadata(mask);
+			int32 size = sizeof(const InterfacesMetadata *) + meta->size;
+			void *data = malloc(size);
+			if (!data) { // terminate if we can't allocate memory
 				throw "Can't allocate memory!";
 			}
 
-			for (int i = 0; i < _meta->last; ++i) {
-				int offset = _meta->offsets[i];
+			_data = data;
+			_meta() = meta;
+			for (int i = 0; i < meta->last; ++i) {
+				int offset = meta->offsets[i];
 				if (offset >= 0) {
 					try {
 						InterfaceWraps[i].Construct(_dataptrunsafe(offset), this);
 					} catch (...) {
 						while (i > 0) {
 							--i;
-							offset = _meta->offsets[--i];
+							offset = meta->offsets[--i];
 							if (offset >= 0) {
 								InterfaceWraps[i].Destruct(_dataptrunsafe(offset));
 							}
@@ -663,10 +677,27 @@ public:
 			}
 		}
 	}
+	void UpdateInterfaces(uint64 mask = 0) {
+		if (!_meta()->equals(mask)) {
+			Interfaces tmp(mask);
+			tmp.swap(*this);
+
+			if (_data != zerodata() && tmp._data != zerodata()) {
+				const InterfacesMetadata *meta = _meta(), *wasmeta = tmp._meta();
+				for (int i = 0; i < meta->last; ++i) {
+					int offset = meta->offsets[i], wasoffset = wasmeta->offsets[i];
+					if (offset >= 0 && wasoffset >= 0) {
+						InterfaceWraps[i].Assign(_dataptrunsafe(offset), tmp._dataptrunsafe(wasoffset));
+					}
+				}
+			}
+		}
+	}
 	~Interfaces() {
-		if (_data) {
-			for (int i = 0; i < _meta->last; ++i) {
-				int offset = _meta->offsets[i];
+		if (_data != zerodata()) {
+			const InterfacesMetadata *meta = _meta();
+			for (int i = 0; i < meta->last; ++i) {
+				int offset = meta->offsets[i];
 				if (offset >= 0) {
 					InterfaceWraps[i].Destruct(_dataptrunsafe(offset));
 				}
@@ -677,24 +708,37 @@ public:
 
 	template <typename Type>
 	Type *Get() {
-		return (Type*)_dataptr(_meta->offsets[Type::Index()]);
+		return static_cast<Type*>(_dataptr(_meta()->offsets[Type::Index()]));
 	}
 	template <typename Type>
 	const Type *Get() const {
-		return (const Type*)_dataptr(_meta->offsets[Type::Index()]);
+		return static_cast<const Type*>(_dataptr(_meta()->offsets[Type::Index()]));
 	}
 
 private:
+	static const InterfacesMetadata *ZeroInterfacesMetadata;
+	static void *zerodata() {
+		return &ZeroInterfacesMetadata;
+	}
 
 	void *_dataptrunsafe(int skip) const {
-		return (char*)_data + skip;
+		return (char*)_data + sizeof(const InterfacesMetadata*) + skip;
 	}
 	void *_dataptr(int skip) const {
 		return (skip >= 0) ? _dataptrunsafe(skip) : 0;
 	}
-	const InterfacesMetadata *_meta;
+	const InterfacesMetadata *&_meta() const {
+		return *static_cast<const InterfacesMetadata**>(_data);
+	}
 	void *_data;
 
+	Interfaces(const Interfaces &other);
+	Interfaces &operator=(const Interfaces &other);
+
+	void swap(Interfaces &other) {
+		std::swap(_data, other._data);
+	}
+
 };
 
 template <typename R>
diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp
index c7cbfe3a6..9c6b19c64 100644
--- a/Telegram/SourceFiles/window.cpp
+++ b/Telegram/SourceFiles/window.cpp
@@ -183,7 +183,7 @@ void NotifyWindow::updateNotifyDisplay() {
 
 		QRect rectForName(st::notifyPhotoPos.x() + st::notifyPhotoSize + st::notifyTextLeft, st::notifyTextTop, itemWidth, st::msgNameFont->height);
 		if (!App::passcoded() && cNotifyView() <= dbinvShowName) {
-			if (history->peer->isChat()) {
+			if (history->peer->isChat() || history->peer->isMegagroup()) {
 				p.drawPixmap(QPoint(rectForName.left() + st::dlgChatImgPos.x(), rectForName.top() + st::dlgChatImgPos.y()), App::sprite(), st::dlgChatImg);
 				rectForName.setLeft(rectForName.left() + st::dlgImgSkip);
 			} else if (history->peer->isChannel()) {
@@ -645,7 +645,7 @@ void Window::sendServiceHistoryRequest() {
 		int32 userFlags = MTPDuser::flag_first_name | MTPDuser::flag_phone | MTPDuser::flag_status | MTPDuser::flag_verified;
 		user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user(MTP_int(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint(), MTPstring(), MTPstring())));
 	}
-	_serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), main->rpcDone(&MainWidget::serviceHistoryDone), main->rpcFail(&MainWidget::serviceHistoryFail));
+	_serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), main->rpcDone(&MainWidget::serviceHistoryDone), main->rpcFail(&MainWidget::serviceHistoryFail));
 }
 
 void Window::setupMain(bool anim, const MTPUser *self) {
@@ -1995,6 +1995,7 @@ LastCrashedWindow::LastCrashedWindow()
 : _port(80)
 , _label(this)
 , _pleaseSendReport(this)
+, _yourReportName(this)
 , _minidump(this)
 , _report(this)
 , _send(this)
@@ -2007,7 +2008,7 @@ LastCrashedWindow::LastCrashedWindow()
 , _reportText(QString::fromUtf8(Sandbox::LastCrashDump()))
 , _reportShown(false)
 , _reportSaved(false)
-, _sendingState(((!cDevVersion() && !cBetaVersion()) || Sandbox::LastCrashDump().isEmpty()) ? SendingNoReport : SendingUpdateCheck)
+, _sendingState(Sandbox::LastCrashDump().isEmpty() ? SendingNoReport : SendingUpdateCheck)
 , _updating(this)
 , _sendingProgress(0)
 , _sendingTotal(0)
@@ -2018,7 +2019,9 @@ LastCrashedWindow::LastCrashedWindow()
 , _updatingSkip(this, false)
 #endif
 {
-
+	if (!cDevVersion() && !cBetaVersion()) { // currently accept crash reports only from testers
+		_sendingState = SendingNoReport;
+	}
 	if (_sendingState != SendingNoReport) {
 		qint64 dumpsize = 0;
 		QString dumpspath = cWorkingDir() + qsl("tdata/dumps");
@@ -2061,8 +2064,18 @@ LastCrashedWindow::LastCrashedWindow()
                 _minidumpFull = maxDumpFull;
             }
         }
-
-		_minidump.setText(qsl("+ %1 (%2 KB)").arg(_minidumpName).arg(dumpsize / 1024));
+		if (_minidumpName.isEmpty()) { // currently don't accept crash reports without dumps from google libraries
+			_sendingState = SendingNoReport;
+		} else {
+			_minidump.setText(qsl("+ %1 (%2 KB)").arg(_minidumpName).arg(dumpsize / 1024));
+		}
+	}
+	if (_sendingState != SendingNoReport) {
+		QString version = getReportField(qstr("version"), qstr("Version:"));
+		QString current = cBetaVersion() ? qsl("-%1").arg(cBetaVersion()) : QString::number(AppVersion);
+		if (version != current) { // currently don't accept crash reports from not current app version
+			_sendingState = SendingNoReport;
+		}
 	}
 
 	_networkSettings.setText(qsl("NETWORK SETTINGS"));
@@ -2105,6 +2118,9 @@ LastCrashedWindow::LastCrashedWindow()
 #endif
 
 	_pleaseSendReport.setText(qsl("Please send us a crash report."));
+	_yourReportName.setText(qsl("Your crash report tag: %1").arg(_minidumpName));
+	_yourReportName.setCursor(style::cur_text);
+	_yourReportName.setTextInteractionFlags(Qt::TextSelectableByMouse);
 
 	_report.setPlainText(_reportText);
 
@@ -2192,7 +2208,7 @@ void LastCrashedWindow::onSendReport() {
 	App::setProxySettings(_sendManager);
 
 	QString apiid = getReportField(qstr("apiid"), qstr("ApiId:")), version = getReportField(qstr("version"), qstr("Version:"));
-	_checkReply = _sendManager.get(QNetworkRequest(qsl("https://tdesktop.com/crash.php?act=query_report&apiid=%1&version=%2&dmp=%3").arg(apiid).arg(version).arg(minidumpFileName().isEmpty() ? 0 : 1)));
+	_checkReply = _sendManager.get(QNetworkRequest(qsl("https://tdesktop.com/crash.php?act=query_report&apiid=%1&version=%2&dmp=%3&platform=%4").arg(apiid).arg(version).arg(minidumpFileName().isEmpty() ? 0 : 1).arg(cPlatformString())));
 
 	connect(_checkReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onSendingError(QNetworkReply::NetworkError)));
 	connect(_checkReply, SIGNAL(finished()), this, SLOT(onCheckingFinished()));
@@ -2411,6 +2427,7 @@ void LastCrashedWindow::updateControls() {
 		_sendSkip.hide();
 		_continue.hide();
 		_pleaseSendReport.hide();
+		_yourReportName.hide();
 		_getApp.hide();
 		_showReport.hide();
 		_report.hide();
@@ -2427,6 +2444,7 @@ void LastCrashedWindow::updateControls() {
 			h += padding + _updatingCheck.height() + padding;
 			if (_sendingState == SendingNoReport) {
 				_pleaseSendReport.hide();
+				_yourReportName.hide();
 				_getApp.hide();
 				_showReport.hide();
 				_report.hide();
@@ -2436,14 +2454,17 @@ void LastCrashedWindow::updateControls() {
 				_sendSkip.hide();
 				_continue.show();
 			} else {
-				h += _showReport.height() + padding;
+				h += _showReport.height() + padding + _yourReportName.height() + padding;
 				_pleaseSendReport.show();
+				_yourReportName.show();
 				if (_sendingState == SendingTooOld || _sendingState == SendingUnofficial) {
 					QString verStr = getReportField(qstr("version"), qstr("Version:"));
 					qint64 ver = verStr.isEmpty() ? 0 : verStr.toLongLong();
 					if (!ver || (ver == AppVersion) || (ver < 0 && (-ver / 1000) == AppVersion)) {
 						h += _getApp.height() + padding;
 						_getApp.show();
+						h -= _yourReportName.height() + padding; // hide report name
+						_yourReportName.hide();
 					} else {
 						_getApp.hide();
 					}
@@ -2498,6 +2519,7 @@ void LastCrashedWindow::updateControls() {
 		} else {
 			_getApp.hide();
 			_pleaseSendReport.hide();
+			_yourReportName.hide();
 			_showReport.hide();
 			_report.hide();
 			_minidump.hide();
@@ -2519,6 +2541,7 @@ void LastCrashedWindow::updateControls() {
 	h += padding + _send.height() + padding;
 	if (_sendingState == SendingNoReport) {
 		_pleaseSendReport.hide();
+		_yourReportName.hide();
 		_showReport.hide();
 		_report.hide();
 		_minidump.hide();
@@ -2528,8 +2551,9 @@ void LastCrashedWindow::updateControls() {
 		_continue.show();
 		_networkSettings.hide();
 	} else {
-		h += _showReport.height() + padding;
+		h += _showReport.height() + padding + _yourReportName.height() + padding;
 		_pleaseSendReport.show();
+		_yourReportName.show();
 		if (_reportShown) {
 			h += (_pleaseSendReport.height() * 12.5) + padding + (_minidumpName.isEmpty() ? 0 : (_minidump.height() + padding));
 			_report.show();
@@ -2771,6 +2795,7 @@ void LastCrashedWindow::resizeEvent(QResizeEvent *e) {
 #ifndef TDESKTOP_DISABLE_AUTOUPDATE
 	_pleaseSendReport.move(padding, padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + (_showReport.height() - _pleaseSendReport.height()) / 2);
 	_showReport.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding);
+	_yourReportName.move(padding, _showReport.y() + _showReport.height() + padding);
 	_getApp.move((width() - _getApp.width()) / 2, _showReport.y() + _showReport.height() + padding);
 
 	if (_sendingState == SendingFail || _sendingState == SendingProgress) {
@@ -2791,11 +2816,11 @@ void LastCrashedWindow::resizeEvent(QResizeEvent *e) {
 
 	_pleaseSendReport.move(padding, padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding + (_showReport.height() - _pleaseSendReport.height()) / 2);
 	_showReport.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding);
+	_yourReportName.move(padding, _showReport.y() + _showReport.height() + padding);
 
 	_networkSettings.move(padding * 2 + _pleaseSendReport.width(), padding * 2 + _networkSettings.height() + _networkSettings.height() + padding + _getApp.height() + padding);
 #endif
-
-	_report.setGeometry(padding, _showReport.y() + _showReport.height() + padding, width() - 2 * padding, _pleaseSendReport.height() * 12.5);
+	_report.setGeometry(padding, _yourReportName.y() + _yourReportName.height() + padding, width() - 2 * padding, _pleaseSendReport.height() * 12.5);
 	_minidump.move(padding, _report.y() + _report.height() + padding);
 	_saveReport.move(_showReport.x(), _showReport.y());
 
diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h
index 4efcdd318..63c1675d0 100644
--- a/Telegram/SourceFiles/window.h
+++ b/Telegram/SourceFiles/window.h
@@ -459,7 +459,7 @@ private:
 	QString _host, _username, _password;
 	quint32 _port;
 
-	PreLaunchLabel _label, _pleaseSendReport, _minidump;
+	PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
 	PreLaunchLog _report;
 	PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
 
diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist
index 9e4f73636..8bc5a45e0 100644
--- a/Telegram/Telegram.plist
+++ b/Telegram/Telegram.plist
@@ -11,7 +11,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>0.9.21</string>
+	<string>0.9.22</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleURLTypes</key>
diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc
index 353487466..8dd9cd58c 100644
--- a/Telegram/Telegram.rc
+++ b/Telegram/Telegram.rc
@@ -34,8 +34,8 @@ IDI_ICON1               ICON                    "SourceFiles\\art\\icon256.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,9,21,0
- PRODUCTVERSION 0,9,21,0
+ FILEVERSION 0,9,22,0
+ PRODUCTVERSION 0,9,22,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -51,10 +51,10 @@ BEGIN
         BLOCK "040904b0"
         BEGIN
             VALUE "CompanyName", "Telegram Messenger LLP"
-            VALUE "FileVersion", "0.9.21.0"
+            VALUE "FileVersion", "0.9.22.0"
             VALUE "LegalCopyright", "Copyright (C) 2014-2016"
             VALUE "ProductName", "Telegram Desktop"
-            VALUE "ProductVersion", "0.9.21.0"
+            VALUE "ProductVersion", "0.9.22.0"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj
index 0e234ad04..8454cab83 100644
--- a/Telegram/Telegram.xcodeproj/project.pbxproj
+++ b/Telegram/Telegram.xcodeproj/project.pbxproj
@@ -1720,7 +1720,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.9.21;
+				CURRENT_PROJECT_VERSION = 0.9.22;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
 				GCC_OPTIMIZATION_LEVEL = 0;
@@ -1739,7 +1739,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				COPY_PHASE_STRIP = YES;
-				CURRENT_PROJECT_VERSION = 0.9.21;
+				CURRENT_PROJECT_VERSION = 0.9.22;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_OPTIMIZATION_LEVEL = fast;
 				GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h;
@@ -1768,10 +1768,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.9.21;
+				CURRENT_PROJECT_VERSION = 0.9.22;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DYLIB_COMPATIBILITY_VERSION = 0.9;
-				DYLIB_CURRENT_VERSION = 0.9.21;
+				DYLIB_CURRENT_VERSION = 0.9.22;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
 				GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1909,10 +1909,10 @@
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CODE_SIGN_IDENTITY = "";
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0.9.21;
+				CURRENT_PROJECT_VERSION = 0.9.22;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DYLIB_COMPATIBILITY_VERSION = 0.9;
-				DYLIB_CURRENT_VERSION = 0.9.21;
+				DYLIB_CURRENT_VERSION = 0.9.22;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
 				FRAMEWORK_SEARCH_PATHS = "";
diff --git a/Telegram/Version b/Telegram/Version
index 58252de8f..bcf8c071a 100644
--- a/Telegram/Version
+++ b/Telegram/Version
@@ -1,6 +1,6 @@
-AppVersion         9021
+AppVersion         9022
 AppVersionStrMajor 0.9
-AppVersionStrSmall 0.9.21
-AppVersionStr      0.9.21
+AppVersionStrSmall 0.9.22
+AppVersionStr      0.9.22
 DevChannel         1
 BetaVersion        0 9019002