Anyone can take a great photo and everyone does. We've all taken a great photo. Whether by luck or skill, each of us has captured a shot that we love and that sums up a moment, a trip, a time in our lives. You don't need an expensive, high spec camera. The simplest camera can take brilliant images when used with creatively. At one time or another we've all succeeded and I guess that makes photography a democratic art form, which is nice.
Lazy masquerading as reflective. I take fewer and fewer photos these days, preferring to take in what I'm experiencing rather than take obvious shots that I'll never look at again. The things that capture my attention are quiet sights, perhaps not noticed by others. The contrast created by the shadow of a wall or a cloud towering over the landscape below it. These are interesting to me. They capture individual moments that show hint at a life, a person, and unique perspective.
I don't set out to capture anything in particular and often go long periods without taking a single photograph. The photographs that I do take increasingly show the average scenes of life. I now live in a rural area. Such places are fantastic to photograph. The weird symmetry between the bucolic and the industrial, or the traditional versus the modern. I find rural areas to be more interesting than cities and I guess that's defining what I now capture when I shoot right now.
you have traveled all over the world. what is it that you like about traveling?
Everyone who has the opportunity to travel should. We are one of the first generations fortunate enough to have access to affordable, reliable, and fast transport all over the planet. The world is not how we perceive it to be from our isolated, solitary views. You need to go out and experience it firsthand. It changes you for the better and you'll find-out just how little you know.
No single image comes to mind. I'm terrible at photographing food so don't bother. I also think most photos of food are utterly pointless so that probably doesn't help.
what is the most remote place you have traveled to?
When I travelled in India I spent most of my time in the very north, in the state of Kashmir and Jammu. This region is contested and though part of India, is also claimed by Pakistan and China. Nestled within the Himalayan mountains of the region, Ladakh is a place of tremendous natural beauty and the welcoming traditional culture of western Tibet. Travelling in the mountains requires guides and permits given the physical challenges and political sensitivity of the region, and while there I journeyed near the Chinese border and at one point, up to 6000 metres above sea level. I have never been to a more beautiful or seductive place. I didn't want to leave that place.
I have many! My photos from that time are some of my favourite however go against everything I've said about capturing the small and ordinary moments of life! The landscape of that region is monumental and should be seen by all. It puts things in perspective for you. We are very small, very weak, and ultimately, very inconsequential. Those mountains were here long before us and will be here long after we are gone. They don't care about us. They will be just fine. That might sound depressing but I find it very reassuring. We shouldn't be so arrogant to think that we matter.


text:
Город (активный) Created with Sketch. Chennai
There are 1 advertisements of Fair in Chennai
question cloud
06:58 13 September 2022
Questioncloud, India's Largest Online Educational Assessment Portal, offers candidates an Online Test Series consisting of previous year question papers, mock tests, and model tests. We provide test series for all major competitive exams, such as TNPSC, UPSC, SSC, RRB, TRB, TNUSRB, RBI, SBI, IBPS, and others.
The official notification for the TNUSRB Recruitment 2022 has not yet been made public. Candidates should check the TNUSRB's official website for the most recent announcements regarding vacancies and the recruitment process.
For more information, visit https://www.questioncloud.in/exam/master/?id=111381#111381
07:21 5 September 2022
Mock test for TN TET exam 2022
The TNTET hall ticket 2022 will be released in the first week of September 2022. The details regarding the exam date, time and exam center are provided through the TNTET hall ticket 2022. The TNTET hall ticket download 2022 link will be uploaded by the TRB on its official website.
Question Cloud, an online educational assessment portal, offers free test series on competitive exams such as TRB, TN TET, TNPSC, SSC, UPSC, NEET, Banking exams and more. The highlights of the Question Cloud's TET test series is it provides results immediately after the test is submitted, as well as solutions to all of the questions.
#questioncloud #tettestseries #tetexam2022 #tetexam
https://www.questioncloud.in/exam/master/?id=111443#111443
07:21 26 August 2022
The mock tests for IBPS RRB Office Assistant exams provided at Question Cloud are drafted by the expert faculties holding a good experience in their respective fields. This makes the IBPS RRB Mock Test 2022 not only best to practice but also completely reliable. Also, for the candidate's benefit, the updated IBPS RRB Clerk Mock Test 2022 is provided here as per the latest syllabus and exam pattern.
For more information, visit https://www.questioncloud.in/exam/master/?id=1027#1027
The SSC CGL is a combined competitive examination administered by the SSC to select candidates for Group B and C positions in various offices and departments. Utilize Question Cloud, India's Largest Online Educational Assessment Portal, to assess your level of preparation.
We offer SSC CGL mock tests that are based on the most recent exam pattern, with topic-by-topic approaches to help you prepare. SSC CGL mock tes that are based on the most recent exam pattern, with topic-by-topic approaches to help you prepare.
SSC CGL free mock test will help you identify your key areas of strength and weakness.
For more visit: https://www.questioncloud.in/exam/master/?id=107#107
#ssc #sscexam #sscexam2022 #sscexamprep
Best App for CBSE Students
Question Cloud - India's Largest Online Learning and Assessment Platform offers the Best video lectures and test series for the students who are studying in CBSE board. Question Cloud's features are described below.
Download our app now: https://play.google.com/store/apps/details?id=io.questioncloud
The Extensive Test series to Revise, Practice
Our test series is intended to provide you with realistic practice and to help you gain confidence in yourself before taking actual exams. Students can access chapter-by-chapter tests on all subjects from classes 6 to 12 as well as mock tests, model tests and previous year's question papers.
Test Reports for Better Practice, Self Evaluation
Following the completion of the tests, we provide detailed reports on the test taken with detailed explanations for every question including questions answered incorrectly as well as correctly. These reports give valuable insights into how well prepared you are for the exam and what areas still need more work so that you can focus on those areas during your next practice test.
The Video lectures to Stop memorizing, Start understanding
Our video lectures are designed to ensure that the students understand the concepts and have a solid grasp of them. This could help the students to boost their preparations by easily understanding the concepts without forgetting them. Our video lectures cover the CBSE classes 6 to 12 based on the most recent syllabus.
For More Information, Visit Us: https://www.questioncloud.in/cbse/
Highlights from Question Cloud's Features for CBSE Students
Class-wise / Subject-wise / Unit-wise / Chapter-wise Tests
Scientifically designed adaptive learning programs
AR & VR based Video classes
Live Classroom / Classroom on Demand
NCERT Solutions & Study Materials
Webinars on Discussion Forums & Doubt Clearing
Special Career Guidance Consultations for What to study after Class 12
Academic counseling for excellence in education
TNPSC Group 1 Notification 2022 is available
TNPSC Group 1 Notice 2022 is Available: The Deputy Collector, Deputy Superintendent of Police, Assistant Commissioner, and other Group 1 positions have 92 openings, according to the Tamil Nadu Public Service Commission (TNPSC). On July 21, 2022, the comprehensive TNPSC Group 1 Notification 2022 was published online.
The registration link for the TNPSC Group 1 Application Form 2022 will be available from July 21 to August 22, and interested and qualified candidates can submit their applications online.
Download the Notification on the given link: https://www.tnpsc.gov.in/Document/english/Group%20-I%20Notification_English.pdf
Aspirants preparing for the TNPSC Group 1 exam can revise their preparation and make sure their readiness for the exam by taking mock tests, model tests, and previous year questions with Question Cloud.
Question Cloud is India's Largest Online Educational Assessment Portal, and provides an online test series for TNPSC group exams including TNPSC Group 1, TNPSC Group 2 & 2A, and TNPSC Group 4 (VAO exams). Candidates interested in taking the test series can avail themselves of it in the Question Cloud platform, also they can practice sample tests for free.
Visit us now on the following link, to take your assessments for the TNPSC Group 1 exam 2022 for Free. Visit us: https://www.questioncloud.in/
Important Dates for TNPSC Group 1 exam - 2022
TNPSC Group 1 Activities
TNPSC Group 1 Notification 2022
TNPSC Group 1 Online Registration Starts
22nd August 2022 (11:59 pm)
Last Date to Pay the Application Fee
TNPSC Group 1 Hall Ticket 2022
TNPSC Group 1 Prelims Date 2022
30th October 2022 (9:30 am to 12:30 pm)
TNPSC Group 1 Mains Exam Date 2022
To be notified
Crash Course for TNUSRB PC 2022
(Including Physical Training)
TNUSRB Recruitment Board is currently accepting applications for the jobs of Grade-II Police Constables, Grade-II Jail Warders, Firemen from qualified and experienced individuals. Merit-based criteria will be used to choose candidates.
Register today with Question Cloud to take part in the full crash course and take a free practice test. Question Cloud is India's largest online educational assessment portal offers a comprehensive crash course in both online and offline modes. Register now, at https://www.questioncloud.in
The Complete Crash course training sessions for TNUSRB Police Constable 2022, will shortly begin in Question Cloud. With our thorough lectures, substantial test series and specialized study resources that are offered throughout the program, candidates can enroll in this program and ace the exam very easily.
Call us right away to obtain a complimentary admission.
Features and Details of the Training sessions
There are both on-campus and online course options.
Separate hostels are located for men and women.
Daily exams, 10 practice exams, plus a playground and gym.
Location of the residential course: Chengalpattu District's Aksheyaa College of Arts and Science.
Details on fees: Online coaching costs 5000 rupees, while residential coaching costs 15000 rupees.
To reserve your free seat, call us right now at +91 782 402 5188 +91 782 402 6188 +91 994 056 9880.
Practice Tests for the CBSE Class 12 Subjects
Subject-specific practice tests with multiple-choice questions are available for CBSE Class 12 on Question Cloud, an online educational assessment platform. With the help of Question Cloud's practice examinations, students can review their preparation. Since revision would help the students retain the information during the exam. The best way to prepare for an exam is to complete practice and questions from Question Cloud that follow the most recent pattern. So that you can get used to the types of questions that will be on the test. Your assurance in passing the final exam and earning excellent grades will therefore grow.
For More Information, Visit: https://www.questioncloud.in/
Major characteristics of the CBSE Class 12 Practice Papers:
· Prepared by experienced teachers
· Most recent CBSE Class 12 Sample Papers served as a guide when creating
· According to the available solutions and grading methodology for the streamlined CBSE Class 12 Term 2 Syllabus
· Quickly downloaded for use when necessary
12:52 9 July 2022
NEET 2022 Preparation strategies
For NEET 2022, Question Cloud - India's Largest Online Educational Assessment Portal offers a series of online tests that will help students prepare effectively by assessing their knowledge. The test series is available on Question Cloud, organized by chapter. To perform better in NEET 2022, take the tests right away. The tactics listed below are crucial for every candidate's preparation.
Solve Papers from Previous Years
This strategy can be quite helpful for candidates who are seeking to study on their own for the NEET 2022 exam.
How to prepare for NEET 2022 using mock tests
The best way for students studying for the NEET without coaching is to conduct self-analysis through online mock tests.
Take free Mock tests at: https://www.questioncloud.in/
Self-discipline is needed for NEET 2022 preparation
When preparing for the NEET exam on your own without a coach, be sincere with yourself. Since there is no one else they can rely on, candidates must be self-disciplined regarding their study routines, whether they are taking regular classes or exams.
Make time to relax and take care of yourself
This could seem unattainable when you're under a lot of preparation-related stress. However, applicants must always keep in mind how crucial it is to release tension.
TNUSRB PC Exam Notification 2022
The TNUSRB Recruitment Board is now inviting applications from qualified and experienced candidates for the positions of Grade-II Police Constables, Grade-II Jail Warders & Firemen. The selection process will be based on merit.
Question Cloud - India's Largest Online Educational Assessment Portal offers an extensive test series for TNUSRB PC exams, Register now with Question Cloud to attempt a mock test for free. Read more about the notification below.
The Tamil Nadu Uniformed Services Recruitment Board (TNUSRB) PC Notification 2022 has been released by the board on its official website. Applicants who wish to apply for these vacancies can do so by downloading the application form from the official website of TNUSRB.
The Tamil Nadu Uniformed Services Recruitment Board (TNUSRB) PC Notification 2022 online application commencing on 7th July 2022 till 15th August 2022.
TNUSRB PC Notification 2022 Date
TNUSRB PC Notification 2022 Apply Online
TNUSRB PC Notification 2022 Last Date to Apply
TNUSRB PC Exam Date 2022
For the positions of Grade-II Police Constables, Grade-II Jail Warders, and Firemen under the Tamil Nadu government, there are a total of 3552 vacancies. Cracking this exam and filling one position among the above-mentioned number of vacancies needs consistent preparation, revision and evaluation. To ease your preparation and crack the exam effectively, Question Cloud offers online test series and mock tests for TNUSRB Police Constable Exams - 2022. Visit us: https://www.questioncloud.in/
13:15 29 June 2022
Online test series for SSC CGL exam
Question Cloud - India's Largest Online Educational Assessment Portal offers an online test series for the SSC CGL exam which is a comprehensive, complete and accurate guide for aspirants who are preparing for this exam. After taking our mock exams, aspirants will be able to gauge their level of preparation, which would make it simpler for them to successfully and efficiently study.
Question Cloud has been developed to help candidates prepare for their SSC CGL exams. We have the largest database of questions of all the exam series and provide them in a highly-customizable format. We also have a dedicated section for all you need to know about SSC CGL, including important tips for success in the exam and how to prepare for it.
The most important benefit of using Question Cloud is that it is completely free! You can use our test series without paying any money for trying out the sample tests. This means that you will not have to worry about your finances while preparing for SSC CGL and you can easily manage your time better too!
Our online test series for SSC CGL exam is designed to help you study and prepare for the exam. We have all the resources you need to prepare for the exam. You can take our mock tests and study material in a single window, without having to switch between websites.
We've compiled our own collection of best practice questions that will help you prepare for the toughest questions on the exam. These questions are designed by experts who have been teaching candidates for years, so they're sure to be relevant and accurate.
The SSC CGL is a very challenging exam, which requires you to have a thorough knowledge of the topics in the syllabus. If you are preparing for this exam and are looking for some quality revision material, then you should visit our website at https://www.questioncloud.in/exam/
Here, we provide all the test series for SSC CGL preparation with both previous year exams and topic-wise mock tests for better understanding. We also provide detailed study materials that will help candidates prepare for their exam in the right manner at the earliest. Our experts have created a complete set of sample papers that can be used as a guideline while preparing for this entrance exam.
Karnataka state board test series
Prepare for Karnataka State Board of Secondary Education Class VI to XII with India's Largest Online Educational Assessment Portal Test Series based on latest pattern. Get Free Mock Tests, Sample Papers and Study Materials. By clicking the link provided below, you can access these tests. Completely mapped to Karnataka State Board, Objective type questions of different types of levels and difficulty, Tests on each topic covering all chapters
For KSEB students in grades 6 through 12, Question Cloud provides online assessment examinations with access to all subject-specific practice exams. With more than 5000 questions, it offers free sample exams for every subject.
Students can prepare for their exams with a test series and mock exams on Question Cloud, an online learning and assessment platform. Additionally, the platform provides an area for schools and other organizations where they can publish exam questions for their students to take.
By logging up with their school accounts or their mobile numbers, students can access the mock exams. They can select the test they want to take after logging in.
For important topics like math, physics, chemistry, and biology, mock exams offer additional practice. Analyzing their preparation will be beneficial for the students.
Students can improve their test-taking performance by thoroughly practicing their questions on the online mock exams.
For more details, visit https://www.questioncloud.in/
Online Tests series for IBPS PO Exam
QuestionCloud is one of the best online test preparation for IBPS Clerk and other Banking Exams. It offers the best quality of questions for practice to the candidates in an easy way.
IBPS Clerk Exam is the largest banking exam. The aspirants who have passed out from any of the higher secondary or technical institutions can make a career in the banking sector. The IBPS Clerk exam is a national level exam which is conducted by Indian Banks' Recruitment Board (IBRB). This is an entrance level examination and candidates who have cleared this examination will be eligible to apply for various jobs available in banks such as head clerk, assistant manager and others.
QuestionCloud has a wide variety of options for aspirants to choose from. You can take our IBPS PO Mock Test which is based on the latest pattern of questions asked by the examiners at IBPS Exam. The mock test will help you to understand what the actual exam is like and give you an idea of how you should prepare yourself for it. It has been designed by experts who have years of experience in this field.
For more information, visit https://www.questioncloud.in/
The Mock Test will help you practice your skills and make sure that you are prepared enough for the exam. You can also use it as a reference guide when preparing for other exams, such as LDC or RRB PO/Clerk Exam etc..
Mock tests for TNTET Exam - 2022
The official notification states that the first paper of the TN TET Exam will be held in August 2022. This exam is required for Tamil Nadu's elementary and upper primary teacher recruitment. A free mock test series on the TN TET Exam 2022 is available from Question Cloud, to ease your preparations and crack the exams simply.
The results of the TN TET exam will be valid for seven years. To better understand TNTET 2022 and get ready for the test, TN TET aspirants should read this post to get a map for better preparations.
Aspirants preparing for the TN TET exam 2022 will go more smoothly if you revise thoroughly and practice with the mock exams and previous year's question papers. For TN TET mock exams and all previous year's question papers with solutions, please visit our website at https://www.questioncloud.in/
A free test series on competitive exams, including TRB, TN TET, TNPSC, SSC, UPSC, NEET, banking exams, and more, is available from Question Cloud, an online educational assessment portal. The features of the Question Cloud exam series that stand out are the instantaneous results and detailed answers to every question. These solutions may aid in raising the preparation's level by enabling one to understand the evaluations of the preparation. Take the test at Question Cloud, and be the best in your exam.
Question Cloud - online educational assessment platform, offers subject-specific practice exams with multiple-choice questions for CBSE Class 10. Students can revise their preparation with Question Cloud's practice exams. Since revision will make it easier for the students to retain information throughout the exam. The ideal strategy to review is to complete practice exams and questions available on Question Cloud based on the most recent pattern so that you can become accustomed to the kinds of questions that will be on the exam paper. Your confidence in taking the final exam and achieving exceptional grades would ultimately increase as a result.
Prepared by seasoned educators
Crafted using the most recent CBSE Class 10 Sample Papers as a guide
According to the simplified CBSE Class 10 Term 2 Syllabus
With solutions and a grading system available
Can be quickly downloaded for usage as needed
Best Aptitude tests for Competitive exams
Question Cloud - India's Largest Online Educational Assessment Portal offers the Best Aptitude tests for Competitive exams such as UPSC, SSC, TNPSC, and all banking exams including IBPS, SBI, RBI and so on. Visit Question Cloud on the following link to take aptitude tests to master your aptitude knowledge. Visit link: https://www.questioncloud.in/
What is an aptitude test?
An aptitude test is a type of test that measures an individual's level of intelligence or mental abilities. Aptitude tests are used to predict how successful an individual will be at their job based on their ability to solve problems, think critically, and learn new skills.
10:56 7 June 2022
TNPSC Group 4 - Preparation tips
The Tamil Nadu Public Services Commission holds the TNPSC Group 4 exam every year to recruit qualified candidates for the positions of Junior Assistant, Bill Collector, Typist, Village Administrative Officer (VAO) Officer, and Steno-Typist with a minimum qualification of 10th standard. TNPSC Group 4 Exam is held offline, followed by document verification.
Similarly for this year, the TNPSC group 4 exam is scheduled to be held on July 24, 2022 (9:30 a.m. to 12:30 p.m.), with a total vacancy of 7301 positions for various departments such as Village Administrative Officer (VAO) Officer, Junior Assistant (Non–Security), Junior Assistant (Security), Bill Collector Grade-I, Typist, Steno-Typist (Grade–III), etc.,
TNPSC Group IV - Preparation Tips:
The expert faculties from Question Cloud, who have years of experience in teaching and taking questions for the TNPSC Group 4 exams, have stated the top five preparation tips for TNPSC Group 4 exams. Question Cloud is India's Largest Online Educational Assessment Portal, where you can practice for competitive exams by answering MCQ-style questions with solutions. Visit us at: https://www.questioncloud.in/
Maintaining your schedule
It is in the best interests of things to create and stick to a study schedule in order to pass the TNPSC Group-IV Services examination. Set aside enough time each day to study and complete the syllabus according to your schedule.
Taking a Mock Exam
If you want to get hands-on practice for the TNPSC Group-IV Services exam, incorporating a mock-test-based learning approach will be extremely beneficial. In fact, mock tests are required for any type of examination. This will allow you to simulate a real-time exam environment and analyze your performance as well as your level of preparation. Give more time to the areas that are lagging. Start taking your mock tests with Question Cloud, Where you can able to access the tests based on topic-wise patterns, so it will be easier for you to take your assessments upon finishing the preparations of the topic studied. Take tests here: https://www.questioncloud.in/exam
Understand the exam pattern and syllabus.
A thorough understanding of the exam pattern for TNPSC Group 4 services is critical in order to pass the exam.
Solve Previous Year's Exam Papers
Solving previous question papers will help you understand the exam pattern that the commission has used in previous years. It is recommended that you practice as many previous years' question papers as possible. It will help you understand the exam pattern, difficulty level, and types of questions that may be asked in the exam. It is unquestionably possible to pass the TNPSC Group exam 4 examinations with the right study materials, proper preparation, and dedication. As a result, ramp up your preparations for the exam.
Regularly revise and practice
Aspirants for the TNPSC Group 4 Examination should stick to a strict study schedule. Creating an effective study plan is a good way to manage your time effectively so that aspirants can finish all of the topics before the exam.
UPSC Preliminaries 2022 - Post exam analysis
The UPSC CSE Prelims Exam was held on June 5, 2022. The exam was divided into two shifts. The morning shift (paper 1, also known as the General studies exam 1) began at 9:30 a.m. and ended at 11:30 a.m., while the afternoon shift (paper 2, also known as the CSAT exam) was from 2:30 p.m. to 4:30 p.m. Candidates who pass the preliminary exam will be invited to take the main exam. The expected cut-offs and question paper are listed below.
Question Cloud's experts offer their thoughts on this year's UPSC Prelims exam question papers. They are pleased with the UPSC Question Paper, describing it as a well-balanced paper with excellent questions from static GK and current affairs. "Especially good this time was the economics questions, which were both conceptual and practical application-based."
They also stated that more than 60% of the UPSC preliminary questions were drawn from the Question Cloud's test series. Aspirants looking for a question paper and its answer key can find them on our website at https://www.questioncloud.in/
Our experts believe that the expected cut-off will rise but remain less than 100 this year. Our experts have also shared the anticipated cut-off for this year. According to the information received, experts believe that the cut-off for this year will be between 95 and 105. If this occurs, those who meet the cut-off marks will be eligible to sit for the UPSC CSE(main) examination 2022, which will begin on September 16, 2022.
Aspirants have been invited to Question Cloud to prepare for the UPSC CSE(Main) Examination with our Questions. Our experts are confident that the questions available in Question Cloud will account for more than half of the marks in the UPSC CSE(Main) Examination 2022. Begin practicing right away at https://www.questioncloud.in/exam
Notification for SBI Clerk 2022
The State Bank of India will soon release the official notification for the SBI Clerk 2022 exam, which may be announced in June 2022 to recruit Junior Associate (Customer Support and Sales). Along with the notification, the SBI will reveal the vacancy details, Prelims and Mains exam dates, Eligibility, Exam Pattern, and so on. SBI is regarded as one of the top banking organizations in India, and for candidates who are banking aspirants and eager to join the banking sector, the SBI Junior Associate position is the best way to get into this organization. Check out more information about this exam and its preparation strategy below.
To effectively prepare for this exam, aspirants should follow a strict study plan along with regular assessments on the topic studied. To have your capability assessments, here we are presenting Question Cloud, it's India's Largest Online Educational Assessment Portal that lets you prepare and assess your knowledge for SBI Clerk 2022 exam.
Visit Question Cloud now to get your assessments done on the SBI Clerk exam preparations. Website link: https://www.questioncloud.in/
SBI Clerk Job Opening 2022
SBI will announce the vacancy for SBI Clerk 2022 in conjunction with its detailed notification, which will be made available on SBI's official website in June 2022. Last year, SBI announced 5454 vacancies for the SBI Clerk 2021 exam.

redpajama_set_name: RedPajamaCommonCrawl


Practice is underway at CRCRC. The track is pretty cool. The best layout they have had since changing to the Ohio RC Factory.
My cars all feel pretty good.
Go get them Jared. I'm praying for you brother.
What 4WD are you running? 5? 6 proto?


Explore our Corporate Tax Retainer Services.
The chargeable income of your company may be different from the net profit/ loss shown in its accounts. This is because some of the expenses incurred by your company may not be deductible for tax purposes. Similarly, some of the income received by your company may not be taxable or it may be taxed separately as a non-trade source income. Besides, you may wish to claim for capital allowances on your fixed assets or claim for unutilised losses/ capital allowances brought forward from your previous Years of Assessment.


Otherwise known as a pouch, dear Reader!
Friday afternoon's workshop was a leather-working event in which a selection of pre-paid attendees created a small leather pouch using kits provided. A similar kit is available through Tandy Leather, proud sponsors of all Steampunk endeavors.
Each piece was pre-cut and had holes pre-punched for the lacings, but a pair of leather scissors and an awl would do the same jobs for the creative crafter. The participants began by staining their leather pieces in various colors, taking some care to avoid staining the epidermal "leather" that covers their hands and arms as well. While not strictly required, I am told it is easier to stain the pieces prior to assembly.
The belt loop snap and front clasp required hammering and a snap setter, as well as a hard concrete surface, so participants lined up to engage in that activity using the sole mallet available. Then, it was time to stitch.
Each kit came with waxed thread and two needles. To get a good, solid seam, one places a needle on each end of the thread, then runs it through the first hole until the halfway point, so that the needles are on opposite sides of the leather pieces. From there, one runs each needle through the next hole, switching sides. Continue thusly, not unlike lacing a corset, until you reach the end, then run back along the seam until you run out of thread or patience.
After assembly, one can embellish the pouch with gears, embossing, tooling, and so forth. Later in the weekend, I participated in a tooling workshop, which I will cover at a later time, dear Reader, so that you might learn somewhat of the craft and perhaps decide to engage further in such pursuits.
In all, an excellent workshop, well worth the time. I saw several of the finished products and, while I am sure your average Eagle Scout would scoff at such works, for those of us unfamiliar with the medium, it was a worthy pursuit.
What do you do when you're not spending money?


text:
package com.google.android.exoplayer2;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.PriorityHandlerThread;
import com.google.android.exoplayer2.util.StandaloneMediaClock;
import com.google.android.exoplayer2.util.TraceUtil;
import java.io.IOException;

/**
 * Implements the internal behavior of {@link ExoPlayerImpl}.
 */
/* package */ final class ExoPlayerImplInternal implements Handler.Callback,
    MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener {

  /**
   * Playback position information which is read on the application's thread by
   * {@link ExoPlayerImpl} and read/written internally on the player's thread.
   */
  public static final class PlaybackInfo {

    public final int periodIndex;
    public final long startPositionUs;

    public volatile long positionUs;
    public volatile long bufferedPositionUs;

    public PlaybackInfo(int periodIndex, long startPositionUs) {
      this.periodIndex = periodIndex;
      this.startPositionUs = startPositionUs;
      positionUs = startPositionUs;
      bufferedPositionUs = startPositionUs;
    }

    public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
      PlaybackInfo playbackInfo = new PlaybackInfo(periodIndex, startPositionUs);
      playbackInfo.positionUs = positionUs;
      playbackInfo.bufferedPositionUs = bufferedPositionUs;
      return playbackInfo;
    }

  }

  public static final class SourceInfo {

    public final Timeline timeline;
    public final Object manifest;
    public final PlaybackInfo playbackInfo;
    public final int seekAcks;

    public SourceInfo(Timeline timeline, Object manifest, PlaybackInfo playbackInfo, int seekAcks) {
      this.timeline = timeline;
      this.manifest = manifest;
      this.playbackInfo = playbackInfo;
      this.seekAcks = seekAcks;
    }

  }

  private static final String TAG = "ExoPlayerImplInternal";

  // External messages
  public static final int MSG_STATE_CHANGED = 1;
  public static final int MSG_LOADING_CHANGED = 2;
  public static final int MSG_TRACKS_CHANGED = 3;
  public static final int MSG_SEEK_ACK = 4;
  public static final int MSG_POSITION_DISCONTINUITY = 5;
  public static final int MSG_SOURCE_INFO_REFRESHED = 6;
  public static final int MSG_ERROR = 7;

  // Internal messages
  private static final int MSG_PREPARE = 0;
  private static final int MSG_SET_PLAY_WHEN_READY = 1;
  private static final int MSG_DO_SOME_WORK = 2;
  private static final int MSG_SEEK_TO = 3;
  private static final int MSG_STOP = 4;
  private static final int MSG_RELEASE = 5;
  private static final int MSG_REFRESH_SOURCE_INFO = 6;
  private static final int MSG_PERIOD_PREPARED = 7;
  private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 8;
  private static final int MSG_TRACK_SELECTION_INVALIDATED = 9;
  private static final int MSG_CUSTOM = 10;

  private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
  private static final int RENDERING_INTERVAL_MS = 10;
  private static final int IDLE_INTERVAL_MS = 1000;

  /**
   * Limits the maximum number of periods to buffer ahead of the current playing period. The
   * buffering policy normally prevents buffering too far ahead, but the policy could allow too many
   * small periods to be buffered if the period count were not limited.
   */
  private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100;

  /**
   * Offset added to all sample timestamps read by renderers to make them non-negative. This is
   * provided for convenience of sources that may return negative timestamps due to prerolling
   * samples from a keyframe before their first sample with timestamp zero, so it must be set to a
   * value greater than or equal to the maximum key-frame interval in seekable periods.
   */
  private static final int RENDERER_TIMESTAMP_OFFSET_US = 60000000;

  private final Renderer[] renderers;
  private final RendererCapabilities[] rendererCapabilities;
  private final TrackSelector trackSelector;
  private final LoadControl loadControl;
  private final StandaloneMediaClock standaloneMediaClock;
  private final Handler handler;
  private final HandlerThread internalPlaybackThread;
  private final Handler eventHandler;
  private final ExoPlayer player;
  private final Timeline.Window window;
  private final Timeline.Period period;

  private PlaybackInfo playbackInfo;
  private Renderer rendererMediaClockSource;
  private MediaClock rendererMediaClock;
  private MediaSource mediaSource;
  private Renderer[] enabledRenderers;
  private boolean released;
  private boolean playWhenReady;
  private boolean rebuffering;
  private boolean isLoading;
  private int state;
  private int customMessagesSent;
  private int customMessagesProcessed;
  private long elapsedRealtimeUs;

  private int pendingInitialSeekCount;
  private SeekPosition pendingSeekPosition;
  private long rendererPositionUs;

  private MediaPeriodHolder loadingPeriodHolder;
  private MediaPeriodHolder readingPeriodHolder;
  private MediaPeriodHolder playingPeriodHolder;

  private Timeline timeline;

  public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
      LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
      PlaybackInfo playbackInfo, ExoPlayer player) {
    this.renderers = renderers;
    this.trackSelector = trackSelector;
    this.loadControl = loadControl;
    this.playWhenReady = playWhenReady;
    this.eventHandler = eventHandler;
    this.state = ExoPlayer.STATE_IDLE;
    this.playbackInfo = playbackInfo;
    this.player = player;

    rendererCapabilities = new RendererCapabilities[renderers.length];
    for (int i = 0; i < renderers.length; i++) {
      renderers[i].setIndex(i);
      rendererCapabilities[i] = renderers[i].getCapabilities();
    }
    standaloneMediaClock = new StandaloneMediaClock();
    enabledRenderers = new Renderer[0];
    window = new Timeline.Window();
    period = new Timeline.Period();
    trackSelector.init(this);

    // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
    // not normally change to this priority" is incorrect.
    internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",
        Process.THREAD_PRIORITY_AUDIO);
    internalPlaybackThread.start();
    handler = new Handler(internalPlaybackThread.getLooper(), this);
  }

  public void prepare(MediaSource mediaSource, boolean resetPosition) {
    handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource)
        .sendToTarget();
  }

  public void setPlayWhenReady(boolean playWhenReady) {
    handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget();
  }

  public void seekTo(Timeline timeline, int windowIndex, long positionUs) {
    handler.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs))
        .sendToTarget();
  }

  public void stop() {
    handler.sendEmptyMessage(MSG_STOP);
  }

  public void sendMessages(ExoPlayerMessage... messages) {
    if (released) {
      Log.w(TAG, "Ignoring messages sent after release.");
      return;
    }
    customMessagesSent++;
    handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget();
  }

  public synchronized void blockingSendMessages(ExoPlayerMessage... messages) {
    if (released) {
      Log.w(TAG, "Ignoring messages sent after release.");
      return;
    }
    int messageNumber = customMessagesSent++;
    handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget();
    while (customMessagesProcessed <= messageNumber) {
      try {
        wait();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  public synchronized void release() {
    if (released) {
      return;
    }
    handler.sendEmptyMessage(MSG_RELEASE);
    while (!released) {
      try {
        wait();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
    internalPlaybackThread.quit();
  }

  // MediaSource.Listener implementation.

  @Override
  public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
    handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget();
  }

  // MediaPeriod.Callback implementation.

  @Override
  public void onPrepared(MediaPeriod source) {
    handler.obtainMessage(MSG_PERIOD_PREPARED, source).sendToTarget();
  }

  @Override
  public void onContinueLoadingRequested(MediaPeriod source) {
    handler.obtainMessage(MSG_SOURCE_CONTINUE_LOADING_REQUESTED, source).sendToTarget();
  }

  // TrackSelector.InvalidationListener implementation.

  @Override
  public void onTrackSelectionsInvalidated() {
    handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
  }

  // Handler.Callback implementation.

  @SuppressWarnings("unchecked")
  @Override
  public boolean handleMessage(Message msg) {
    try {
      switch (msg.what) {
        case MSG_PREPARE: {
          prepareInternal((MediaSource) msg.obj, msg.arg1 != 0);
          return true;
        }
        case MSG_SET_PLAY_WHEN_READY: {
          setPlayWhenReadyInternal(msg.arg1 != 0);
          return true;
        }
        case MSG_DO_SOME_WORK: {
          doSomeWork();
          return true;
        }
        case MSG_SEEK_TO: {
          seekToInternal((SeekPosition) msg.obj);
          return true;
        }
        case MSG_STOP: {
          stopInternal();
          return true;
        }
        case MSG_RELEASE: {
          releaseInternal();
          return true;
        }
        case MSG_PERIOD_PREPARED: {
          handlePeriodPrepared((MediaPeriod) msg.obj);
          return true;
        }
        case MSG_REFRESH_SOURCE_INFO: {
          handleSourceInfoRefreshed((Pair<Timeline, Object>) msg.obj);
          return true;
        }
        case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
          handleContinueLoadingRequested((MediaPeriod) msg.obj);
          return true;
        }
        case MSG_TRACK_SELECTION_INVALIDATED: {
          reselectTracksInternal();
          return true;
        }
        case MSG_CUSTOM: {
          sendMessagesInternal((ExoPlayerMessage[]) msg.obj);
          return true;
        }
        default:
          return false;
      }
    } catch (ExoPlaybackException e) {
      Log.e(TAG, "Renderer error.", e);
      eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
      stopInternal();
      return true;
    } catch (IOException e) {
      Log.e(TAG, "Source error.", e);
      eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget();
      stopInternal();
      return true;
    } catch (RuntimeException e) {
      Log.e(TAG, "Internal runtime error.", e);
      eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e))
          .sendToTarget();
      stopInternal();
      return true;
    }
  }

  // Private methods.

  private void setState(int state) {
    if (this.state != state) {
      this.state = state;
      eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget();
    }
  }

  private void setIsLoading(boolean isLoading) {
    if (this.isLoading != isLoading) {
      this.isLoading = isLoading;
      eventHandler.obtainMessage(MSG_LOADING_CHANGED, isLoading ? 1 : 0, 0).sendToTarget();
    }
  }

  private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
    resetInternal(true);
    loadControl.onPrepared();
    if (resetPosition) {
      playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
    }
    this.mediaSource = mediaSource;
    mediaSource.prepareSource(player, true, this);
    setState(ExoPlayer.STATE_BUFFERING);
    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
  }

  private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException {
    rebuffering = false;
    this.playWhenReady = playWhenReady;
    if (!playWhenReady) {
      stopRenderers();
      updatePlaybackPositions();
    } else {
      if (state == ExoPlayer.STATE_READY) {
        startRenderers();
        handler.sendEmptyMessage(MSG_DO_SOME_WORK);
      } else if (state == ExoPlayer.STATE_BUFFERING) {
        handler.sendEmptyMessage(MSG_DO_SOME_WORK);
      }
    }
  }

  private void startRenderers() throws ExoPlaybackException {
    rebuffering = false;
    standaloneMediaClock.start();
    for (Renderer renderer : enabledRenderers) {
      renderer.start();
    }
  }

  private void stopRenderers() throws ExoPlaybackException {
    standaloneMediaClock.stop();
    for (Renderer renderer : enabledRenderers) {
      ensureStopped(renderer);
    }
  }

  private void updatePlaybackPositions() throws ExoPlaybackException {
    if (playingPeriodHolder == null) {
      return;
    }

    // Update the playback position.
    long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity();
    if (periodPositionUs != C.TIME_UNSET) {
      resetRendererPosition(periodPositionUs);
    } else {
      if (rendererMediaClockSource != null && !rendererMediaClockSource.isEnded()) {
        rendererPositionUs = rendererMediaClock.getPositionUs();
        standaloneMediaClock.setPositionUs(rendererPositionUs);
      } else {
        rendererPositionUs = standaloneMediaClock.getPositionUs();
      }
      periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs);
    }
    playbackInfo.positionUs = periodPositionUs;
    elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;

    // Update the buffered position.
    long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
        : playingPeriodHolder.mediaPeriod.getBufferedPositionUs();
    playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE
        ? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs()
        : bufferedPositionUs;
  }

  private void doSomeWork() throws ExoPlaybackException, IOException {

    long operationStartTimeMs = SystemClock.elapsedRealtime();
    updatePeriods();
    if (playingPeriodHolder == null) {
      // We're still waiting for the first period to be prepared.
      maybeThrowPeriodPrepareError();
      scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
      return;
    }

    TraceUtil.beginSection("doSomeWork");

    updatePlaybackPositions();
    playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs);

    boolean allRenderersEnded = true;
    boolean allRenderersReadyOrEnded = true;
    for (Renderer renderer : enabledRenderers) {
      // TODO: Each renderer should return the maximum delay before which it wishes to be called
      // again. The minimum of these values should then be used as the delay before the next
      // invocation of this method.
      renderer.render(rendererPositionUs, elapsedRealtimeUs);
      allRenderersEnded = allRenderersEnded && renderer.isEnded();
      // Determine whether the renderer is ready (or ended). If it's not, throw an error that's
      // preventing the renderer from making progress, if such an error exists.
      boolean rendererReadyOrEnded = renderer.isReady() || renderer.isEnded();
      if (!rendererReadyOrEnded) {
        renderer.maybeThrowStreamError();
      }
      allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
    }

    if (!allRenderersReadyOrEnded) {
      maybeThrowPeriodPrepareError();
    }

    long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
        .getDurationUs();
    if (allRenderersEnded
        && (playingPeriodDurationUs == C.TIME_UNSET
        || playingPeriodDurationUs <= playbackInfo.positionUs)
        && playingPeriodHolder.isLast) {
      setState(ExoPlayer.STATE_ENDED);
      stopRenderers();
    } else if (state == ExoPlayer.STATE_BUFFERING) {
      boolean isNewlyReady = enabledRenderers.length > 0
          ? (allRenderersReadyOrEnded && haveSufficientBuffer(rebuffering))
          : isTimelineReady(playingPeriodDurationUs);
      if (isNewlyReady) {
        setState(ExoPlayer.STATE_READY);
        if (playWhenReady) {
          startRenderers();
        }
      }
    } else if (state == ExoPlayer.STATE_READY) {
      boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded
          : isTimelineReady(playingPeriodDurationUs);
      if (!isStillReady) {
        rebuffering = playWhenReady;
        setState(ExoPlayer.STATE_BUFFERING);
        stopRenderers();
      }
    }

    if (state == ExoPlayer.STATE_BUFFERING) {
      for (Renderer renderer : enabledRenderers) {
        renderer.maybeThrowStreamError();
      }
    }

    if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
      scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS);
    } else if (enabledRenderers.length != 0) {
      scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
    } else {
      handler.removeMessages(MSG_DO_SOME_WORK);
    }

    TraceUtil.endSection();
  }

  private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) {
    handler.removeMessages(MSG_DO_SOME_WORK);
    long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs;
    long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime();
    if (nextOperationDelayMs <= 0) {
      handler.sendEmptyMessage(MSG_DO_SOME_WORK);
    } else {
      handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, nextOperationDelayMs);
    }
  }

  private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException {
    if (timeline == null) {
      pendingInitialSeekCount++;
      pendingSeekPosition = seekPosition;
      return;
    }

    Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
    if (periodPosition == null) {
      // The seek position was valid for the timeline that it was performed into, but the
      // timeline has changed and a suitable seek position could not be resolved in the new one.
      playbackInfo = new PlaybackInfo(0, 0);
      eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
      // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't
      // ignored.
      playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
      setState(ExoPlayer.STATE_ENDED);
      // Reset, but retain the source so that it can still be used should a seek occur.
      resetInternal(false);
      return;
    }

    boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
    int periodIndex = periodPosition.first;
    long periodPositionUs = periodPosition.second;

    try {
      if (periodIndex == playbackInfo.periodIndex
          && ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) {
        // Seek position equals the current position. Do nothing.
        return;
      }
      long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
      seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
      periodPositionUs = newPeriodPositionUs;
    } finally {
      playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
      eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
          .sendToTarget();
    }
  }

  private long seekToPeriodPosition(int periodIndex, long periodPositionUs)
      throws ExoPlaybackException {
    stopRenderers();
    rebuffering = false;
    setState(ExoPlayer.STATE_BUFFERING);

    MediaPeriodHolder newPlayingPeriodHolder = null;
    if (playingPeriodHolder == null) {
      // We're still waiting for the first period to be prepared.
      if (loadingPeriodHolder != null) {
        loadingPeriodHolder.release();
      }
    } else {
      // Clear the timeline, but keep the requested period if it is already prepared.
      MediaPeriodHolder periodHolder = playingPeriodHolder;
      while (periodHolder != null) {
        if (periodHolder.index == periodIndex && periodHolder.prepared) {
          newPlayingPeriodHolder = periodHolder;
        } else {
          periodHolder.release();
        }
        periodHolder = periodHolder.next;
      }
    }

    // Disable all the renderers if the period being played is changing, or if the renderers are
    // reading from a period other than the one being played.
    if (playingPeriodHolder != newPlayingPeriodHolder
        || playingPeriodHolder != readingPeriodHolder) {
      for (Renderer renderer : enabledRenderers) {
        renderer.disable();
      }
      enabledRenderers = new Renderer[0];
      rendererMediaClock = null;
      rendererMediaClockSource = null;
      playingPeriodHolder = null;
    }

    // Update the holders.
    if (newPlayingPeriodHolder != null) {
      newPlayingPeriodHolder.next = null;
      loadingPeriodHolder = newPlayingPeriodHolder;
      readingPeriodHolder = newPlayingPeriodHolder;
      setPlayingPeriodHolder(newPlayingPeriodHolder);
      if (playingPeriodHolder.hasEnabledTracks) {
        periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
      }
      resetRendererPosition(periodPositionUs);
      maybeContinueLoading();
    } else {
      loadingPeriodHolder = null;
      readingPeriodHolder = null;
      playingPeriodHolder = null;
      resetRendererPosition(periodPositionUs);
    }

    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
    return periodPositionUs;
  }

  private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
    rendererPositionUs = playingPeriodHolder == null
        ? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US
        : playingPeriodHolder.toRendererTime(periodPositionUs);
    standaloneMediaClock.setPositionUs(rendererPositionUs);
    for (Renderer renderer : enabledRenderers) {
      renderer.resetPosition(rendererPositionUs);
    }
  }

  private void stopInternal() {
    resetInternal(true);
    loadControl.onStopped();
    setState(ExoPlayer.STATE_IDLE);
  }

  private void releaseInternal() {
    resetInternal(true);
    loadControl.onReleased();
    setState(ExoPlayer.STATE_IDLE);
    synchronized (this) {
      released = true;
      notifyAll();
    }
  }

  private void resetInternal(boolean releaseMediaSource) {
    handler.removeMessages(MSG_DO_SOME_WORK);
    rebuffering = false;
    standaloneMediaClock.stop();
    rendererMediaClock = null;
    rendererMediaClockSource = null;
    rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
    for (Renderer renderer : enabledRenderers) {
      try {
        ensureStopped(renderer);
        renderer.disable();
      } catch (ExoPlaybackException | RuntimeException e) {
        // There's nothing we can do.
        Log.e(TAG, "Stop failed.", e);
      }
    }
    enabledRenderers = new Renderer[0];
    releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder
        : loadingPeriodHolder);
    loadingPeriodHolder = null;
    readingPeriodHolder = null;
    playingPeriodHolder = null;
    setIsLoading(false);
    if (releaseMediaSource) {
      if (mediaSource != null) {
        mediaSource.releaseSource();
        mediaSource = null;
      }
      timeline = null;
    }
  }

  private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException {
    try {
      for (ExoPlayerMessage message : messages) {
        message.target.handleMessage(message.messageType, message.message);
      }
      if (mediaSource != null) {
        // The message may have caused something to change that now requires us to do work.
        handler.sendEmptyMessage(MSG_DO_SOME_WORK);
      }
    } finally {
      synchronized (this) {
        customMessagesProcessed++;
        notifyAll();
      }
    }
  }

  private void ensureStopped(Renderer renderer) throws ExoPlaybackException {
    if (renderer.getState() == Renderer.STATE_STARTED) {
      renderer.stop();
    }
  }

  private void reselectTracksInternal() throws ExoPlaybackException {
    if (playingPeriodHolder == null) {
      // We don't have tracks yet, so we don't care.
      return;
    }
    // Reselect tracks on each period in turn, until the selection changes.
    MediaPeriodHolder periodHolder = playingPeriodHolder;
    boolean selectionsChangedForReadPeriod = true;
    while (true) {
      if (periodHolder == null || !periodHolder.prepared) {
        // The reselection did not change any prepared periods.
        return;
      }
      if (periodHolder.selectTracks()) {
        // Selected tracks have changed for this period.
        break;
      }
      if (periodHolder == readingPeriodHolder) {
        // The track reselection didn't affect any period that has been read.
        selectionsChangedForReadPeriod = false;
      }
      periodHolder = periodHolder.next;
    }

    if (selectionsChangedForReadPeriod) {
      // Update streams and rebuffer for the new selection, recreating all streams if reading ahead.
      boolean recreateStreams = readingPeriodHolder != playingPeriodHolder;
      releasePeriodHoldersFrom(playingPeriodHolder.next);
      playingPeriodHolder.next = null;
      loadingPeriodHolder = playingPeriodHolder;
      readingPeriodHolder = playingPeriodHolder;

      boolean[] streamResetFlags = new boolean[renderers.length];
      long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
          playbackInfo.positionUs, recreateStreams, streamResetFlags);
      if (periodPositionUs != playbackInfo.positionUs) {
        playbackInfo.positionUs = periodPositionUs;
        resetRendererPosition(periodPositionUs);
      }

      int enabledRendererCount = 0;
      boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
      for (int i = 0; i < renderers.length; i++) {
        Renderer renderer = renderers[i];
        rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
        SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
        if (sampleStream != null) {
          enabledRendererCount++;
        }
        if (rendererWasEnabledFlags[i]) {
          if (sampleStream != renderer.getStream()) {
            // We need to disable the renderer.
            if (renderer == rendererMediaClockSource) {
              // The renderer is providing the media clock.
              if (sampleStream == null) {
                // The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
                // over timing responsibilities.
                standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
              }
              rendererMediaClock = null;
              rendererMediaClockSource = null;
            }
            ensureStopped(renderer);
            renderer.disable();
          } else if (streamResetFlags[i]) {
            // The renderer will continue to consume from its current stream, but needs to be reset.
            renderer.resetPosition(rendererPositionUs);
          }
        }
      }
      eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult)
          .sendToTarget();
      enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
    } else {
      // Release and re-prepare/buffer periods after the one whose selection changed.
      loadingPeriodHolder = periodHolder;
      periodHolder = loadingPeriodHolder.next;
      while (periodHolder != null) {
        periodHolder.release();
        periodHolder = periodHolder.next;
      }
      loadingPeriodHolder.next = null;
      if (loadingPeriodHolder.prepared) {
        long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.startPositionUs,
            loadingPeriodHolder.toPeriodTime(rendererPositionUs));
        loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false);
      }
    }
    maybeContinueLoading();
    updatePlaybackPositions();
    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
  }

  private boolean isTimelineReady(long playingPeriodDurationUs) {
    return playingPeriodDurationUs == C.TIME_UNSET
        || playbackInfo.positionUs < playingPeriodDurationUs
        || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared);
  }

  private boolean haveSufficientBuffer(boolean rebuffering) {
    long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
        ? loadingPeriodHolder.startPositionUs
        : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
    if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) {
      if (loadingPeriodHolder.isLast) {
        return true;
      }
      loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period)
          .getDurationUs();
    }
    return loadControl.shouldStartPlayback(
        loadingPeriodBufferedPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs),
        rebuffering);
  }

  private void maybeThrowPeriodPrepareError() throws IOException {
    if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared
        && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) {
      for (Renderer renderer : enabledRenderers) {
        if (!renderer.hasReadStreamToEnd()) {
          return;
        }
      }
      loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError();
    }
  }

  private void handleSourceInfoRefreshed(Pair<Timeline, Object> timelineAndManifest)
      throws ExoPlaybackException {
    Timeline oldTimeline = timeline;
    timeline = timelineAndManifest.first;
    Object manifest = timelineAndManifest.second;

    int processedInitialSeekCount = 0;
    if (oldTimeline == null) {
      if (pendingInitialSeekCount > 0) {
        Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingSeekPosition);
        processedInitialSeekCount = pendingInitialSeekCount;
        pendingInitialSeekCount = 0;
        pendingSeekPosition = null;
        if (periodPosition == null) {
          // The seek position was valid for the timeline that it was performed into, but the
          // timeline has changed and a suitable seek position could not be resolved in the new one.
          handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
          return;
        }
        playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
      } else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
        if (timeline.isEmpty()) {
          handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
          return;
        }
        Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
        playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second);
      }
    }

    MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder
        : loadingPeriodHolder;
    if (periodHolder == null) {
      // We don't have any period holders, so we're done.
      notifySourceInfoRefresh(manifest, processedInitialSeekCount);
      return;
    }

    int periodIndex = timeline.getIndexOfPeriod(periodHolder.uid);
    if (periodIndex == C.INDEX_UNSET) {
      // We didn't find the current period in the new timeline. Attempt to resolve a subsequent
      // period whose window we can restart from.
      int newPeriodIndex = resolveSubsequentPeriod(periodHolder.index, oldTimeline, timeline);
      if (newPeriodIndex == C.INDEX_UNSET) {
        // We failed to resolve a suitable restart position.
        handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
        return;
      }
      // We resolved a subsequent period. Seek to the default position in the corresponding window.
      Pair<Integer, Long> defaultPosition = getPeriodPosition(
          timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET);
      newPeriodIndex = defaultPosition.first;
      long newPositionUs = defaultPosition.second;
      timeline.getPeriod(newPeriodIndex, period, true);
      // Clear the index of each holder that doesn't contain the default position. If a holder
      // contains the default position then update its index so it can be re-used when seeking.
      Object newPeriodUid = period.uid;
      periodHolder.index = C.INDEX_UNSET;
      while (periodHolder.next != null) {
        periodHolder = periodHolder.next;
        periodHolder.index = periodHolder.uid.equals(newPeriodUid) ? newPeriodIndex : C.INDEX_UNSET;
      }
      // Actually do the seek.
      newPositionUs = seekToPeriodPosition(newPeriodIndex, newPositionUs);
      playbackInfo = new PlaybackInfo(newPeriodIndex, newPositionUs);
      notifySourceInfoRefresh(manifest, processedInitialSeekCount);
      return;
    }

    // The current period is in the new timeline. Update the holder and playbackInfo.
    timeline.getPeriod(periodIndex, period);
    boolean isLastPeriod = periodIndex == timeline.getPeriodCount() - 1
        && !timeline.getWindow(period.windowIndex, window).isDynamic;
    periodHolder.setIndex(periodIndex, isLastPeriod);
    boolean seenReadingPeriod = periodHolder == readingPeriodHolder;
    if (periodIndex != playbackInfo.periodIndex) {
      playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex);
    }

    // If there are subsequent holders, update the index for each of them. If we find a holder
    // that's inconsistent with the new timeline then take appropriate action.
    while (periodHolder.next != null) {
      MediaPeriodHolder previousPeriodHolder = periodHolder;
      periodHolder = periodHolder.next;
      periodIndex++;
      timeline.getPeriod(periodIndex, period, true);
      isLastPeriod = periodIndex == timeline.getPeriodCount() - 1
          && !timeline.getWindow(period.windowIndex, window).isDynamic;
      if (periodHolder.uid.equals(period.uid)) {
        // The holder is consistent with the new timeline. Update its index and continue.
        periodHolder.setIndex(periodIndex, isLastPeriod);
        seenReadingPeriod |= (periodHolder == readingPeriodHolder);
      } else {
        // The holder is inconsistent with the new timeline.
        if (!seenReadingPeriod) {
          // Renderers may have read from a period that's been removed. Seek back to the current
          // position of the playing period to make sure none of the removed period is played.
          periodIndex = playingPeriodHolder.index;
          long newPositionUs = seekToPeriodPosition(periodIndex, playbackInfo.positionUs);
          playbackInfo = new PlaybackInfo(periodIndex, newPositionUs);
        } else {
          // Update the loading period to be the last period that's still valid, and release all
          // subsequent periods.
          loadingPeriodHolder = previousPeriodHolder;
          loadingPeriodHolder.next = null;
          // Release the rest of the timeline.
          releasePeriodHoldersFrom(periodHolder);
        }
        break;
      }
    }

    notifySourceInfoRefresh(manifest, processedInitialSeekCount);
  }

  private void handleSourceInfoRefreshEndedPlayback(Object manifest,
      int processedInitialSeekCount) {
    // Set the playback position to (0,0) for notifying the eventHandler.
    playbackInfo = new PlaybackInfo(0, 0);
    notifySourceInfoRefresh(manifest, processedInitialSeekCount);
    // Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored.
    playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
    setState(ExoPlayer.STATE_ENDED);
    // Reset, but retain the source so that it can still be used should a seek occur.
    resetInternal(false);
  }

  private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) {
    eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED,
        new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget();
  }

  /**
   * Given a period index into an old timeline, finds the first subsequent period that also exists
   * in a new timeline. The index of this period in the new timeline is returned.
   *
   * @param oldPeriodIndex The index of the period in the old timeline.
   * @param oldTimeline The old timeline.
   * @param newTimeline The new timeline.
   * @return The index in the new timeline of the first subsequent period, or {@link C#INDEX_UNSET}
   *     if no such period was found.
   */
  private int resolveSubsequentPeriod(int oldPeriodIndex, Timeline oldTimeline,
      Timeline newTimeline) {
    int newPeriodIndex = C.INDEX_UNSET;
    while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
      newPeriodIndex = newTimeline.getIndexOfPeriod(
          oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid);
    }
    return newPeriodIndex;
  }

  /**
   * Converts a {@link SeekPosition} into the corresponding (periodIndex, periodPositionUs) for the
   * internal timeline.
   *
   * @param seekPosition The position to resolve.
   * @return The resolved position, or null if resolution was not successful.
   * @throws IllegalSeekPositionException If the window index of the seek position is outside the
   *     bounds of the timeline.
   */
  private Pair<Integer, Long> resolveSeekPosition(SeekPosition seekPosition) {
    Timeline seekTimeline = seekPosition.timeline;
    if (seekTimeline.isEmpty()) {
      // The application performed a blind seek without a non-empty timeline (most likely based on
      // knowledge of what the future timeline will be). Use the internal timeline.
      seekTimeline = timeline;
    }
    // Map the SeekPosition to a position in the corresponding timeline.
    Pair<Integer, Long> periodPosition;
    try {
      periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex,
          seekPosition.windowPositionUs);
    } catch (IndexOutOfBoundsException e) {
      // The window index of the seek position was outside the bounds of the timeline.
      throw new IllegalSeekPositionException(timeline, seekPosition.windowIndex,
          seekPosition.windowPositionUs);
    }
    if (timeline == seekTimeline) {
      // Our internal timeline is the seek timeline, so the mapped position is correct.
      return periodPosition;
    }
    // Attempt to find the mapped period in the internal timeline.
    int periodIndex = timeline.getIndexOfPeriod(
        seekTimeline.getPeriod(periodPosition.first, period, true).uid);
    if (periodIndex != C.INDEX_UNSET) {
      // We successfully located the period in the internal timeline.
      return Pair.create(periodIndex, periodPosition.second);
    }
    // Try and find a subsequent period from the seek timeline in the internal timeline.
    periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline);
    if (periodIndex != C.INDEX_UNSET) {
      // We found one. Map the SeekPosition onto the corresponding default position.
      return getPeriodPosition(timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET);
    }
    // We didn't find one. Give up.
    return null;
  }

  /**
   * Calls {@link #getPeriodPosition(Timeline, int, long)} using the current timeline.
   */
  private Pair<Integer, Long> getPeriodPosition(int windowIndex, long windowPositionUs) {
    return getPeriodPosition(timeline, windowIndex, windowPositionUs);
  }

  /**
   * Calls {@link #getPeriodPosition(Timeline, int, long, long)} with a zero default position
   * projection.
   */
  private Pair<Integer, Long> getPeriodPosition(Timeline timeline, int windowIndex,
      long windowPositionUs) {
    return getPeriodPosition(timeline, windowIndex, windowPositionUs, 0);
  }

  /**
   * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs).
   *
   * @param timeline The timeline containing the window.
   * @param windowIndex The window index.
   * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default
   *     start position.
   * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the
   *     duration into the future by which the window's position should be projected.
   * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs}
   *     is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's
   *     position could not be projected by {@code defaultPositionProjectionUs}.
   */
  private Pair<Integer, Long> getPeriodPosition(Timeline timeline, int windowIndex,
      long windowPositionUs, long defaultPositionProjectionUs) {
    Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
    timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs);
    if (windowPositionUs == C.TIME_UNSET) {
      windowPositionUs = window.getDefaultPositionUs();
      if (windowPositionUs == C.TIME_UNSET) {
        return null;
      }
    }
    int periodIndex = window.firstPeriodIndex;
    long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs;
    long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs();
    while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
        && periodIndex < window.lastPeriodIndex) {
      periodPositionUs -= periodDurationUs;
      periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs();
    }
    return Pair.create(periodIndex, periodPositionUs);
  }

  private void updatePeriods() throws ExoPlaybackException, IOException {

    if (timeline == null) {
      // We're waiting to get information about periods.
      mediaSource.maybeThrowSourceInfoRefreshError();
      return;
    }

    // Update the loading period if required.
    maybeUpdateLoadingPeriod();
    if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
      setIsLoading(false);
    } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) {
      maybeContinueLoading();
    }

    if (playingPeriodHolder == null) {
      // We're waiting for the first period to be prepared.
      return;
    }

    // Update the playing and reading periods.
    while (playingPeriodHolder != readingPeriodHolder
        && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) {
      // All enabled renderers' streams have been read to the end, and the playback position reached
      // the end of the playing period, so advance playback to the next period.
      playingPeriodHolder.release();
      setPlayingPeriodHolder(playingPeriodHolder.next);
      playbackInfo = new PlaybackInfo(playingPeriodHolder.index,
          playingPeriodHolder.startPositionUs);
      updatePlaybackPositions();
      eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
    }

    if (readingPeriodHolder.isLast) {
      for (int i = 0; i < renderers.length; i++) {
        Renderer renderer = renderers[i];
        SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
        // Defer setting the stream as final until the renderer has actually consumed the whole
        // stream in case of playlist changes that cause the stream to be no longer final.
        if (sampleStream != null && renderer.getStream() == sampleStream
            && renderer.hasReadStreamToEnd()) {
          renderer.setCurrentStreamFinal();
        }
      }
      return;
    }

    for (int i = 0; i < renderers.length; i++) {
      Renderer renderer = renderers[i];
      SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
      if (renderer.getStream() != sampleStream
          || (sampleStream != null && !renderer.hasReadStreamToEnd())) {
        return;
      }
    }

    if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
      TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
      readingPeriodHolder = readingPeriodHolder.next;
      TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult;

      boolean initialDiscontinuity =
          readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET;
      for (int i = 0; i < renderers.length; i++) {
        Renderer renderer = renderers[i];
        TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i);
        if (oldSelection == null) {
          // The renderer has no current stream and will be enabled when we play the next period.
        } else if (initialDiscontinuity) {
          // The new period starts with a discontinuity, so the renderer will play out all data then
          // be disabled and re-enabled when it starts playing the next period.
          renderer.setCurrentStreamFinal();
        } else if (!renderer.isCurrentStreamFinal()) {
          TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
          RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
          RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
          if (newSelection != null && newConfig.equals(oldConfig)) {
            // Replace the renderer's SampleStream so the transition to playing the next period can
            // be seamless.
            Format[] formats = new Format[newSelection.length()];
            for (int j = 0; j < formats.length; j++) {
              formats[j] = newSelection.getFormat(j);
            }
            renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
                readingPeriodHolder.getRendererOffset());
          } else {
            // The renderer will be disabled when transitioning to playing the next period, either
            // because there's no new selection or because a configuration change is required. Mark
            // the SampleStream as final to play out any remaining data.
            renderer.setCurrentStreamFinal();
          }
        }
      }
    }
  }

  private void maybeUpdateLoadingPeriod() throws IOException {
    int newLoadingPeriodIndex;
    if (loadingPeriodHolder == null) {
      newLoadingPeriodIndex = playbackInfo.periodIndex;
    } else {
      int loadingPeriodIndex = loadingPeriodHolder.index;
      if (loadingPeriodHolder.isLast || !loadingPeriodHolder.isFullyBuffered()
          || timeline.getPeriod(loadingPeriodIndex, period).getDurationUs() == C.TIME_UNSET) {
        // Either the existing loading period is the last period, or we are not ready to advance to
        // loading the next period because it hasn't been fully buffered or its duration is unknown.
        return;
      }
      if (playingPeriodHolder != null
          && loadingPeriodIndex - playingPeriodHolder.index == MAXIMUM_BUFFER_AHEAD_PERIODS) {
        // We are already buffering the maximum number of periods ahead.
        return;
      }
      newLoadingPeriodIndex = loadingPeriodHolder.index + 1;
    }

    if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
      // The next period is not available yet.
      mediaSource.maybeThrowSourceInfoRefreshError();
      return;
    }

    long newLoadingPeriodStartPositionUs;
    if (loadingPeriodHolder == null) {
      newLoadingPeriodStartPositionUs = playbackInfo.positionUs;
    } else {
      int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
      if (newLoadingPeriodIndex
          != timeline.getWindow(newLoadingWindowIndex, window).firstPeriodIndex) {
        // We're starting to buffer a new period in the current window. Always start from the
        // beginning of the period.
        newLoadingPeriodStartPositionUs = 0;
      } else {
        // We're starting to buffer a new window. When playback transitions to this window we'll
        // want it to be from its default start position. The expected delay until playback
        // transitions is equal the duration of media that's currently buffered (assuming no
        // interruptions). Hence we project the default start position forward by the duration of
        // the buffer, and start buffering from this point.
        long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset()
            + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
            - rendererPositionUs;
        Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, newLoadingWindowIndex,
            C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
        if (defaultPosition == null) {
          return;
        }

        newLoadingPeriodIndex = defaultPosition.first;
        newLoadingPeriodStartPositionUs = defaultPosition.second;
      }
    }

    long rendererPositionOffsetUs = loadingPeriodHolder == null
        ? newLoadingPeriodStartPositionUs + RENDERER_TIMESTAMP_OFFSET_US
        : (loadingPeriodHolder.getRendererOffset()
            + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs());
    timeline.getPeriod(newLoadingPeriodIndex, period, true);
    boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1
        && !timeline.getWindow(period.windowIndex, window).isDynamic;
    MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
        rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid,
        newLoadingPeriodIndex, isLastPeriod, newLoadingPeriodStartPositionUs);
    if (loadingPeriodHolder != null) {
      loadingPeriodHolder.next = newPeriodHolder;
    }
    loadingPeriodHolder = newPeriodHolder;
    loadingPeriodHolder.mediaPeriod.prepare(this);
    setIsLoading(true);
  }

  private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
    if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
      // Stale event.
      return;
    }
    loadingPeriodHolder.handlePrepared();
    if (playingPeriodHolder == null) {
      // This is the first prepared period, so start playing it.
      readingPeriodHolder = loadingPeriodHolder;
      resetRendererPosition(readingPeriodHolder.startPositionUs);
      setPlayingPeriodHolder(readingPeriodHolder);
    }
    maybeContinueLoading();
  }

  private void handleContinueLoadingRequested(MediaPeriod period) {
    if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
      // Stale event.
      return;
    }
    maybeContinueLoading();
  }

  private void maybeContinueLoading() {
    long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0
        : loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
    if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
      setIsLoading(false);
    } else {
      long loadingPeriodPositionUs = loadingPeriodHolder.toPeriodTime(rendererPositionUs);
      long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
      boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
      setIsLoading(continueLoading);
      if (continueLoading) {
        loadingPeriodHolder.needsContinueLoading = false;
        loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs);
      } else {
        loadingPeriodHolder.needsContinueLoading = true;
      }
    }
  }

  private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
    while (periodHolder != null) {
      periodHolder.release();
      periodHolder = periodHolder.next;
    }
  }

  private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
    if (playingPeriodHolder == periodHolder) {
      return;
    }

    int enabledRendererCount = 0;
    boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
    for (int i = 0; i < renderers.length; i++) {
      Renderer renderer = renderers[i];
      rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
      TrackSelection newSelection = periodHolder.trackSelectorResult.selections.get(i);
      if (newSelection != null) {
        enabledRendererCount++;
      }
      if (rendererWasEnabledFlags[i] && (newSelection == null
          || (renderer.isCurrentStreamFinal()
          && renderer.getStream() == playingPeriodHolder.sampleStreams[i]))) {
        // The renderer should be disabled before playing the next period, either because it's not
        // needed to play the next period, or because we need to re-enable it as its current stream
        // is final and it's not reading ahead.
        if (renderer == rendererMediaClockSource) {
          // Sync standaloneMediaClock so that it can take over timing responsibilities.
          standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
          rendererMediaClock = null;
          rendererMediaClockSource = null;
        }
        ensureStopped(renderer);
        renderer.disable();
      }
    }

    playingPeriodHolder = periodHolder;
    eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult).sendToTarget();
    enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
  }

  private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
      throws ExoPlaybackException {
    enabledRenderers = new Renderer[enabledRendererCount];
    enabledRendererCount = 0;
    for (int i = 0; i < renderers.length; i++) {
      Renderer renderer = renderers[i];
      TrackSelection newSelection = playingPeriodHolder.trackSelectorResult.selections.get(i);
      if (newSelection != null) {
        enabledRenderers[enabledRendererCount++] = renderer;
        if (renderer.getState() == Renderer.STATE_DISABLED) {
          RendererConfiguration rendererConfiguration =
              playingPeriodHolder.trackSelectorResult.rendererConfigurations[i];
          // The renderer needs enabling with its new track selection.
          boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
          // Consider as joining only if the renderer was previously disabled.
          boolean joining = !rendererWasEnabledFlags[i] && playing;
          // Build an array of formats contained by the selection.
          Format[] formats = new Format[newSelection.length()];
          for (int j = 0; j < formats.length; j++) {
            formats[j] = newSelection.getFormat(j);
          }
          // Enable the renderer.
          renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i],
              rendererPositionUs, joining, playingPeriodHolder.getRendererOffset());
          MediaClock mediaClock = renderer.getMediaClock();
          if (mediaClock != null) {
            if (rendererMediaClock != null) {
              throw ExoPlaybackException.createForUnexpected(
                  new IllegalStateException("Multiple renderer media clocks enabled."));
            }
            rendererMediaClock = mediaClock;
            rendererMediaClockSource = renderer;
          }
          // Start the renderer if playing.
          if (playing) {
            renderer.start();
          }
        }
      }
    }
  }

  /**
   * Holds a {@link MediaPeriod} with information required to play it as part of a timeline.
   */
  private static final class MediaPeriodHolder {

    public final MediaPeriod mediaPeriod;
    public final Object uid;
    public final SampleStream[] sampleStreams;
    public final boolean[] mayRetainStreamFlags;
    public final long rendererPositionOffsetUs;

    public int index;
    public long startPositionUs;
    public boolean isLast;
    public boolean prepared;
    public boolean hasEnabledTracks;
    public MediaPeriodHolder next;
    public boolean needsContinueLoading;
    public TrackSelectorResult trackSelectorResult;

    private final Renderer[] renderers;
    private final RendererCapabilities[] rendererCapabilities;
    private final TrackSelector trackSelector;
    private final LoadControl loadControl;
    private final MediaSource mediaSource;

    private TrackSelectorResult periodTrackSelectorResult;

    public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
        long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl,
        MediaSource mediaSource, Object periodUid, int periodIndex, boolean isLastPeriod,
        long startPositionUs) {
      this.renderers = renderers;
      this.rendererCapabilities = rendererCapabilities;
      this.rendererPositionOffsetUs = rendererPositionOffsetUs;
      this.trackSelector = trackSelector;
      this.loadControl = loadControl;
      this.mediaSource = mediaSource;
      this.uid = Assertions.checkNotNull(periodUid);
      this.index = periodIndex;
      this.isLast = isLastPeriod;
      this.startPositionUs = startPositionUs;
      sampleStreams = new SampleStream[renderers.length];
      mayRetainStreamFlags = new boolean[renderers.length];
      mediaPeriod = mediaSource.createPeriod(periodIndex, loadControl.getAllocator(),
          startPositionUs);
    }

    public long toRendererTime(long periodTimeUs) {
      return periodTimeUs + getRendererOffset();
    }

    public long toPeriodTime(long rendererTimeUs) {
      return rendererTimeUs - getRendererOffset();
    }

    public long getRendererOffset() {
      return rendererPositionOffsetUs - startPositionUs;
    }

    public void setIndex(int index, boolean isLast) {
      this.index = index;
      this.isLast = isLast;
    }

    public boolean isFullyBuffered() {
      return prepared
          && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE);
    }

    public void handlePrepared() throws ExoPlaybackException {
      prepared = true;
      selectTracks();
      startPositionUs = updatePeriodTrackSelection(startPositionUs, false);
    }

    public boolean selectTracks() throws ExoPlaybackException {
      TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
          mediaPeriod.getTrackGroups());
      if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
        return false;
      }
      trackSelectorResult = selectorResult;
      return true;
    }

    public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) {
      return updatePeriodTrackSelection(positionUs, forceRecreateStreams,
          new boolean[renderers.length]);
    }

    public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams,
        boolean[] streamResetFlags) {
      TrackSelectionArray trackSelections = trackSelectorResult.selections;
      for (int i = 0; i < trackSelections.length; i++) {
        mayRetainStreamFlags[i] = !forceRecreateStreams
            && trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
      }

      // Disable streams on the period and get new streams for updated/newly-enabled tracks.
      positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
          sampleStreams, streamResetFlags, positionUs);
      periodTrackSelectorResult = trackSelectorResult;

      // Update whether we have enabled tracks and sanity check the expected streams are non-null.
      hasEnabledTracks = false;
      for (int i = 0; i < sampleStreams.length; i++) {
        if (sampleStreams[i] != null) {
          Assertions.checkState(trackSelections.get(i) != null);
          hasEnabledTracks = true;
        } else {
          Assertions.checkState(trackSelections.get(i) == null);
        }
      }

      // The track selection has changed.
      loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
      return positionUs;
    }

    public void release() {
      try {
        mediaSource.releasePeriod(mediaPeriod);
      } catch (RuntimeException e) {
        // There's nothing we can do.
        Log.e(TAG, "Period release failed.", e);
      }
    }

  }

  private static final class SeekPosition {

    public final Timeline timeline;
    public final int windowIndex;
    public final long windowPositionUs;

    public SeekPosition(Timeline timeline, int windowIndex, long windowPositionUs) {
      this.timeline = timeline;
      this.windowIndex = windowIndex;
      this.windowPositionUs = windowPositionUs;
    }

  }

}

redpajama_set_name: RedPajamaGithub


Enter the Kendra Scott Color Bar Party Giveaway Today!
Did you know that Brides of Oklahoma and Kendra Scott have teamed up to give one lucky bride and her maids a Kendra Scott Color Bar Party!? The winner and four of her friends will design their own unique piece of colorful Kendra Scott jewelry! Create and customize with twenty-six stone shades and twenty-three silhouettes to choose from. Head over to the Brides of Oklahoma Instagram post and comment with your favorite colors or color combos to enter. Tag your best gals for an additional entry! Winners will be announced September 1. Good luck!


text:
PiP (NSW)
About PiP (NSW)
Australia's Only Comic Magician and Hypnotist, PiP is gaining International recognition as one of Australia's finest up and coming Entertainers. He has performed and toured around Australia, New Zealand and the USA as well as living and working in Las Vegas.
His Mastery of Prestidigitation and Hypnotic Phenomenon have seen him appear on National Television, entertain at Playboy parties in Las Vegas and perform as one of the youngest Guest Entertainers ever on 5 star Cruise Ships. He was also requested to entertain at major International Conferences such as APEC 2007. He was Nominated for a 2007 MO Award and competed at the World Magic Seminar 2006 as one of only 18 teenagers chosen.
2009 saw PiP performing 46 one man shows during the Melbourne International Comedy Festival, making him probably the hardest working comic of the entire festival and now touring around Australia!
In 2014 he was classified as an 'ALIEN WITH EXTRAORDINARY ABILITY' by the U.S. Government and is now based in Las Vegas, Nevada. His CRITICALLY ACCLAIMED SELL OUT SHOWS are pushing the art form of magic and changing public perception of what they know it to be, thanks to the unique and eccentric style of illusions he creates.
PiP's countless TV appearances include being a FINALIST on AUSTRALIA'S GOT TALENT 2012. His tours have included SOLD OUT SHOWS during the Adelaide Fringe, Sydney Comedy Festival, Melbourne International Comedy Festival, Melbourne Magic Festival & at major venues including the iconic SYDNEY OPERA HOUSE. More recently he has been seen in LAS VEGAS at the The Cosmopolitan, Caesars Palace, The Aria, The Bellagio, The Hard Rock Hotel & Casino, The Downtown Grand, Playboy parties at The Palms & more.
His career highlights include opening for MAJOR AUSTRALIAN ARTISTS such as Justice Crew, Timomatic, Nathaniel and Samantha Jade. Working as a Guest Entertainer on major cruise lines including Royal Caribbean, Celebrity, Princess and Carnival. Being asked by WARNER BROS. to be the face of their campaign for the DVD launch of 'The Incredible Burt Wonderstone', starring Steve Carell and Jim Carrey. Being requested to return and do a second performance during the 2007 APEC WORLD LEADERS CONFERENCE and in 2009 performing 46 One Man Shows at the Melbourne International Comedy Festival in just 4 weeks!
PiP (NSW) Videos


Greetings! Welcome to the lastminute.com's travel website for low cost flights. If you are looking for a low cost flight Tegel-Friedrichshafen, you are in the right place! Your are click away from confirming your flight Tegel-Friedrichshafen.
lastminute.com selects from hundreds of flight deals Tegel-Friedrichshafen some of the best deals that month to save you time. Why not take advantage of the monthly flight offers and book now.
Once your flight Tegel-Friedrichshafen is booked, using lastminute.com you can even organise the rest of your trip. From car hire and accommodation in Friedrichshafen as well as a guide to Friedrichshafen so you know in advance what there is to see and do.
If you are a frequent traveller, lastminute.com would like to be your trusted one stop travel partner in helping you find flights, hotels and car hire in Friedrichshafen and worldwide.
lastminute.com's facebook page gives you an easy way to stay up to date with giveaways and promotions on low cost flights and holidays to Friedrichshafen from Tegel.